pax_global_header00006660000000000000000000000064137365617710014532gustar00rootroot0000000000000052 comment=17f27c1996c75145b8eb5d16583bddcb6e2bf691 btchip-python-0.1.31/000077500000000000000000000000001373656177100144045ustar00rootroot00000000000000btchip-python-0.1.31/.gitignore000066400000000000000000000000071373656177100163710ustar00rootroot00000000000000*.pyc btchip-python-0.1.31/.gitmodules000066400000000000000000000000001373656177100165470ustar00rootroot00000000000000btchip-python-0.1.31/LICENSE000066400000000000000000000261361373656177100154210ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. btchip-python-0.1.31/MANIFEST.in000066400000000000000000000000231373656177100161350ustar00rootroot00000000000000include README.md btchip-python-0.1.31/README.md000066400000000000000000000025311373656177100156640ustar00rootroot00000000000000btchip-python ============= Python communication library for Ledger Hardware Wallet products Requirements ------------- This API is available on pip - install with pip install btchip-python Building on a Unix platform requires libusb-1.0-0-dev and libudev-dev installed previously Interim Debian packages have also been built by Richard Ulrich at https://launchpad.net/~richi-paraeasy/+archive/ubuntu/bitcoin/ (btchip-python, hidapi and python-hidapi) For optional BIP 39 support during dongle setup, also install https://github.com/trezor/python-mnemonic - also available as a Debian package at the previous link (python-mnemonic) Building on Windows -------------------- - Download and install the latest Python 2.7 version from https://www.python.org/downloads/windows/ - Install Microsoft Visual C++ Compiler for Python 2.7 from http://www.microsoft.com/en-us/download/details.aspx?id=44266 - Download and install PyQt4 for Python 2.7 from https://www.riverbankcomputing.com/software/pyqt/download - Install the btchip library (open a command prompt and enter c:\python27\scripts\pip install btchip) Building/Installing on FreeBSD ------------------------------ On FreeBSD you can install the packages: pkg install security/py-btchip-python or build via ports: cd /usr/ports/security/py-btchip-python make install clean btchip-python-0.1.31/btchip/000077500000000000000000000000001373656177100156555ustar00rootroot00000000000000btchip-python-0.1.31/btchip/__init__.py000066400000000000000000000015271373656177100177730ustar00rootroot00000000000000""" ******************************************************************************* * 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. ******************************************************************************** """ __version__ = "0.1.31" btchip-python-0.1.31/btchip/bitcoinTransaction.py000066400000000000000000000114121373656177100220630ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip-python-0.1.31/btchip/bitcoinVarint.py000066400000000000000000000037511373656177100210500ustar00rootroot00000000000000""" ******************************************************************************* * 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") btchip-python-0.1.31/btchip/btchip.py000066400000000000000000000630411373656177100175040ustar00rootroot00000000000000""" ******************************************************************************* * 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 .btchipKeyRecovery 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 Exception: pass try: result = self.getJCExtendedFeatures() self.needKeyCache = (result['proprietaryApi'] == False) except Exception: pass def setAlternateCoinVersion(self, versionRegular, versionP2SH): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SET_ALTERNATE_COIN_VERSION, 0x00, 0x00, 0x02, versionRegular, versionP2SH] self.dongle.exchange(bytearray(apdu)) def verifyPin(self, pin): if isinstance(pin, str): pin = pin.encode('utf-8') apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_VERIFY_PIN, 0x00, 0x00, len(pin) ] apdu.extend(bytearray(pin)) self.dongle.exchange(bytearray(apdu)) def getVerifyPinRemainingAttempts(self): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_VERIFY_PIN, 0x80, 0x00, 0x01 ] apdu.extend(bytearray(b'0')) try: self.dongle.exchange(bytearray(apdu)) except BTChipException as e: if ((e.sw & 0xfff0) == 0x63c0): return e.sw - 0x63c0 raise e 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, continueSegwit=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 = 0x10 if continueSegwit else 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 ('trustedInput' in passedOutput) and passedOutput['trustedInput']: params.append(0x01) elif ('witness' in passedOutput) and passedOutput['witness']: params.append(0x02) 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) 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 if len(script) == 0: apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x80, 0x00, len(sequence) ] apdu.extend(sequence) self.dongle.exchange(bytearray(apdu)) 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 Exception: 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]] else: # Support for old style API before 1.0.2 result['confirmationNeeded'] = response[0] != 0x00 result['confirmationType'] = 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): 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 signMessagePrepareV1(self, path, message): donglePath = parse_bip32_path(path) if self.needKeyCache: self.resolvePublicKeysInPath(path) result = {} apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SIGN_MESSAGE, 0x00, 0x00 ] params = [] params.extend(donglePath) params.append(len(message)) params.extend(bytearray(message)) apdu.append(len(params)) apdu.extend(params) response = self.dongle.exchange(bytearray(apdu)) result['confirmationNeeded'] = response[0] != 0x00 result['confirmationType'] = response[0] if result['confirmationType'] == 0x02: result['keycardData'] = response[1:] if result['confirmationType'] == 0x03: result['secureScreenData'] = response[1:] 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 setup(self, operationModeFlags, featuresFlag, keyVersion, keyVersionP2SH, userPin, wipePin, keymapEncoding, seed=None, developerKey=None): if isinstance(userPin, str): userPin = userPin.encode('utf-8') result = {} apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SETUP, 0x00, 0x00 ] params = [ operationModeFlags, featuresFlag, keyVersion, keyVersionP2SH ] params.append(len(userPin)) params.extend(bytearray(userPin)) if wipePin is not None: if isinstance(wipePin, str): wipePin = wipePin.encode('utf-8') params.append(len(wipePin)) params.extend(bytearray(wipePin)) else: params.append(0x00) if seed is not None: if len(seed) < 32 or len(seed) > 64: raise BTChipException("Invalid seed length") params.append(len(seed)) params.extend(seed) else: params.append(0x00) if developerKey is not None: params.append(len(developerKey)) params.extend(developerKey) else: params.append(0x00) apdu.append(len(params)) apdu.extend(params) response = self.dongle.exchange(bytearray(apdu)) result['trustedInputKey'] = response[0:16] result['developerKey'] = response[16:] self.setKeymapEncoding(keymapEncoding) try: self.setTypingBehaviour(0xff, 0xff, 0xff, 0x10) except BTChipException as e: if (e.sw == 0x6700): # Old firmware version, command not supported pass else: raise return result def setKeymapEncoding(self, keymapEncoding): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SET_KEYMAP, 0x00, 0x00 ] apdu.append(len(keymapEncoding)) apdu.extend(keymapEncoding) self.dongle.exchange(bytearray(apdu)) def setTypingBehaviour(self, unitDelayStart, delayStart, unitDelayKey, delayKey): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SET_KEYMAP, 0x01, 0x00 ] params = [] writeUint32BE(unitDelayStart, params) writeUint32BE(delayStart, params) writeUint32BE(unitDelayKey, params) writeUint32BE(delayKey, params) apdu.append(len(params)) apdu.extend(params) self.dongle.exchange(bytearray(apdu)) def getOperationMode(self): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_OPERATION_MODE, 0x00, 0x00, 0x00] response = self.dongle.exchange(bytearray(apdu)) return response[0] def setOperationMode(self, operationMode): if operationMode != btchip.OPERATION_MODE_WALLET \ and operationMode != btchip.OPERATION_MODE_RELAXED_WALLET \ and operationMode != btchip.OPERATION_MODE_SERVER \ and operationMode != btchip.OPERATION_MODE_DEVELOPER: raise BTChipException("Invalid operation mode") apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SET_OPERATION_MODE, 0x00, 0x00, 0x01, operationMode ] self.dongle.exchange(bytearray(apdu)) def enableAlternate2fa(self, persistent): if persistent: p1 = 0x02 else: p1 = 0x01 apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SET_OPERATION_MODE, p1, 0x00, 0x01, btchip.OPERATION_MODE_WALLET ] self.dongle.exchange(bytearray(apdu)) 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 def getRandom(self, size): if size > 255: raise BTChipException("Invalid size") apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_RANDOM, 0x00, 0x00, size ] return self.dongle.exchange(bytearray(apdu)) def getPOSSeedKey(self): result = {} apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_POS_SEED, 0x01, 0x00, 0x00 ] return self.dongle.exchange(bytearray(apdu)) def getPOSEncryptedSeed(self): result = {} apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_POS_SEED, 0x02, 0x00, 0x00 ] return self.dongle.exchange(bytearray(apdu)) def importPrivateKey(self, data, isSeed=False): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_IMPORT_PRIVATE_KEY, (0x02 if isSeed else 0x01), 0x00 ] apdu.append(len(data)) apdu.extend(data) return self.dongle.exchange(bytearray(apdu)) def getPublicKey(self, encodedPrivateKey): result = {} apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_PUBLIC_KEY, 0x00, 0x00 ] apdu.append(len(encodedPrivateKey) + 1) apdu.append(len(encodedPrivateKey)) apdu.extend(encodedPrivateKey) response = self.dongle.exchange(bytearray(apdu)) offset = 1 result['publicKey'] = response[offset + 1 : offset + 1 + response[offset]] offset = offset + 1 + response[offset] if response[0] == 0x02: result['chainCode'] = response[offset : offset + 32] offset = offset + 32 result['depth'] = response[offset] offset = offset + 1 result['parentFingerprint'] = response[offset : offset + 4] offset = offset + 4 result['childNumber'] = response[offset : offset + 4] return result def deriveBip32Key(self, encodedPrivateKey, path): donglePath = parse_bip32_path(path) if self.needKeyCache: self.resolvePublicKeysInPath(path) offset = 1 currentEncodedPrivateKey = encodedPrivateKey while (offset < len(donglePath)): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_DERIVE_BIP32_KEY, 0x00, 0x00 ] apdu.append(len(currentEncodedPrivateKey) + 1 + 4) apdu.append(len(currentEncodedPrivateKey)) apdu.extend(currentEncodedPrivateKey) apdu.extend(donglePath[offset : offset + 4]) currentEncodedPrivateKey = self.dongle.exchange(bytearray(apdu)) offset = offset + 4 return currentEncodedPrivateKey def signImmediate(self, encodedPrivateKey, data, deterministic=True): apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SIGNVERIFY_IMMEDIATE, 0x00, (0x80 if deterministic else 0x00) ] apdu.append(len(encodedPrivateKey) + len(data) + 2); apdu.append(len(encodedPrivateKey)) apdu.extend(encodedPrivateKey) apdu.append(len(data)) apdu.extend(data) return self.dongle.exchange(bytearray(apdu)) # Functions dedicated to the Java Card interface when no proprietary API is available def parse_bip32_path_internal(self, path): if len(path) == 0: return [] result = [] elements = path.split('/') for pathElement in elements: element = pathElement.split('\'') if len(element) == 1: result.append(int(element[0])) else: result.append(0x80000000 | int(element[0])) return result def serialize_bip32_path_internal(self, path): result = [] for pathElement in path: writeUint32BE(pathElement, result) return bytearray([ len(path) ] + result) def resolvePublicKey(self, path): expandedPath = self.serialize_bip32_path_internal(path) apdu = [ self.BTCHIP_JC_EXT_CLA, self.BTCHIP_INS_EXT_CACHE_HAS_PUBLIC_KEY, 0x00, 0x00 ] apdu.append(len(expandedPath)) apdu.extend(expandedPath) result = self.dongle.exchange(bytearray(apdu)) if (result[0] == 0): # Not present, need to be inserted into the cache apdu = [ self.BTCHIP_JC_EXT_CLA, self.BTCHIP_INS_EXT_GET_HALF_PUBLIC_KEY, 0x00, 0x00 ] apdu.append(len(expandedPath)) apdu.extend(expandedPath) result = self.dongle.exchange(bytearray(apdu)) hashData = result[0:32] keyX = result[32:64] signature = result[64:] keyXY = recoverKey(signature, hashData, keyX) apdu = [ self.BTCHIP_JC_EXT_CLA, self.BTCHIP_INS_EXT_CACHE_PUT_PUBLIC_KEY, 0x00, 0x00 ] apdu.append(len(expandedPath) + 65) apdu.extend(expandedPath) apdu.extend(keyXY) self.dongle.exchange(bytearray(apdu)) def resolvePublicKeysInPath(self, path): splitPath = self.parse_bip32_path_internal(path) # Locate the first public key in path offset = 0 startOffset = 0 while(offset < len(splitPath)): if (splitPath[offset] < 0x80000000): startOffset = offset break offset = offset + 1 if startOffset != 0: searchPath = splitPath[0:startOffset - 1] offset = startOffset - 1 while(offset < len(splitPath)): searchPath = searchPath + [ splitPath[offset] ] self.resolvePublicKey(searchPath) offset = offset + 1 self.resolvePublicKey(splitPath) def getJCExtendedFeatures(self): result = {} apdu = [ self.BTCHIP_JC_EXT_CLA, self.BTCHIP_INS_EXT_CACHE_GET_FEATURES, 0x00, 0x00, 0x00 ] response = self.dongle.exchange(bytearray(apdu)) result['proprietaryApi'] = ((response[0] & 0x01) != 0) return result btchip-python-0.1.31/btchip/btchipComm.py000066400000000000000000000161151373656177100203200ustar00rootroot00000000000000""" ******************************************************************************* * 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 Exception: pass self.opened = False class DongleSmartcard(Dongle): def __init__(self, device, debug=False): self.device = device self.debug = debug self.waitImpl = self self.opened = True def exchange(self, apdu, timeout=20000): if self.debug: print("=> %s" % hexlify(apdu)) response, sw1, sw2 = self.device.transmit(toBytes(hexlify(apdu))) sw = (sw1 << 8) | sw2 if self.debug: print("<= %s%.2x" % (toHexString(response).replace(" ", ""), sw)) if sw != 0x9000: raise BTChipException("Invalid status %04x" % sw, sw) return bytearray(response) def close(self): if self.opened: try: self.device.disconnect() except Exception: pass self.opened = False class DongleServer(Dongle): def __init__(self, server, port, debug=False): self.server = server self.port = port self.debug = debug self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.socket.connect((self.server, self.port)) except Exception: raise BTChipException("Proxy connection failed") def exchange(self, apdu, timeout=20000): if self.debug: print("=> %s" % hexlify(apdu)) self.socket.send(struct.pack(">I", len(apdu))) self.socket.send(apdu) size = struct.unpack(">I", self.socket.recv(4))[0] response = self.socket.recv(size) sw = struct.unpack(">H", self.socket.recv(2))[0] if self.debug: print("<= %s%.2x" % (hexlify(response), sw)) if sw != 0x9000: raise BTChipException("Invalid status %04x" % sw, sw) return bytearray(response) def close(self): try: self.socket.close() except Exception: pass def getDongle(debug=False): dev = None hidDevicePath = None ledger = False if HID: for hidDevice in hid.enumerate(0, 0): if hidDevice['vendor_id'] == 0x2581 and hidDevice['product_id'] == 0x2b7c: hidDevicePath = hidDevice['path'] if hidDevice['vendor_id'] == 0x2581 and hidDevice['product_id'] == 0x3b7c: hidDevicePath = hidDevice['path'] ledger = True if hidDevice['vendor_id'] == 0x2581 and hidDevice['product_id'] == 0x4b7c: hidDevicePath = hidDevice['path'] ledger = True if hidDevice['vendor_id'] == 0x2c97: if ('interface_number' in hidDevice and hidDevice['interface_number'] == 0) or ('usage_page' in hidDevice and hidDevice['usage_page'] == 0xffa0): hidDevicePath = hidDevice['path'] ledger = True if hidDevice['vendor_id'] == 0x2581 and hidDevice['product_id'] == 0x1807: hidDevicePath = hidDevice['path'] if hidDevicePath is not None: dev = hid.device() dev.open_path(hidDevicePath) dev.set_nonblocking(True) return HIDDongleHIDAPI(dev, ledger, debug) if SCARD: connection = None for reader in readers(): try: connection = reader.createConnection() connection.connect() response, sw1, sw2 = connection.transmit(toBytes("00A4040010FF4C4547522E57414C5430312E493031")) sw = (sw1 << 8) | sw2 if sw == 0x9000: break else: connection.disconnect() connection = None except Exception: connection = None pass if connection is not None: return DongleSmartcard(connection, debug) if (os.getenv("LEDGER_PROXY_ADDRESS") is not None) and (os.getenv("LEDGER_PROXY_PORT") is not None): return DongleServer(os.getenv("LEDGER_PROXY_ADDRESS"), int(os.getenv("LEDGER_PROXY_PORT")), debug) raise BTChipException("No dongle found") btchip-python-0.1.31/btchip/btchipException.py000066400000000000000000000017741373656177100213700ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip-python-0.1.31/btchip/btchipFirmwareWizard.py000066400000000000000000000017751373656177100223700ustar00rootroot00000000000000""" ******************************************************************************* * 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. ******************************************************************************** """ def checkFirmware(version): return True def updateFirmware(): raise Exception("Unsupported BTChip firmware - please update your firmware from https://firmwareupdate.hardwarewallet.com") btchip-python-0.1.31/btchip/btchipHelpers.py000066400000000000000000000052311373656177100210240ustar00rootroot00000000000000""" ******************************************************************************* * 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) btchip-python-0.1.31/btchip/btchipKeyRecovery.py000066400000000000000000000035001373656177100216660ustar00rootroot00000000000000# From Electrum import ecdsa from ecdsa.curves import SECP256k1 from ecdsa.ellipticcurve import Point from ecdsa.util import string_to_number, number_to_string class MyVerifyingKey(ecdsa.VerifyingKey): @classmethod def from_signature(klass, sig, recid, h, curve): """ See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """ from ecdsa import util, numbertheory import msqr curveFp = curve.curve G = curve.generator order = G.order() # extract r,s from signature r, s = util.sigdecode_string(sig, order) # 1.1 x = r + (recid/2) * order # 1.3 alpha = ( x * x * x + curveFp.a() * x + curveFp.b() ) % curveFp.p() beta = msqr.modular_sqrt(alpha, curveFp.p()) y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta # 1.4 the constructor checks that nR is at infinity R = Point(curveFp, x, y, order) # 1.5 compute e from message: e = string_to_number(h) minus_e = -e % order # 1.6 compute Q = r^-1 (sR - eG) inv_r = numbertheory.inverse_mod(r,order) Q = inv_r * ( s * R + minus_e * G ) return klass.from_public_point( Q, curve ) def point_to_ser(P): return ( '04'+('%064x'%P.x())+('%064x'%P.y()) ).decode('hex') def recoverKey(signature, hashValue, keyX): rLength = signature[3] r = signature[4 : 4 + rLength] sLength = signature[4 + rLength + 1] s = signature[4 + rLength + 2:] if rLength == 33: r = r[1:] if sLength == 33: s = s[1:] r = str(r) s = str(s) for i in range(4): try: key = MyVerifyingKey.from_signature(r + s, i, hashValue, curve = SECP256k1) candidate = point_to_ser(key.pubkey.point) if candidate[1:33] == keyX: return candidate except Exception: pass raise Exception("Key recovery failed") btchip-python-0.1.31/btchip/btchipPersoWizard.py000066400000000000000000000301731373656177100216760ustar00rootroot00000000000000""" ******************************************************************************* * 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 sys from PyQt4 import QtCore, QtGui from PyQt4.QtGui import QDialog, QMessageBox try: from mnemonic import Mnemonic MNEMONIC = True except Exception: MNEMONIC = False from .btchipComm import getDongle, DongleWait from .btchip import btchip from .btchipUtils import compress_public_key,format_transaction, get_regular_input_script from .bitcoinTransaction import bitcoinTransaction from .btchipException import BTChipException import ui.personalization00start import ui.personalization01seed import ui.personalization02security import ui.personalization03config import ui.personalization04finalize import ui.personalizationseedbackup01 import ui.personalizationseedbackup02 import ui.personalizationseedbackup03 import ui.personalizationseedbackup04 BTCHIP_DEBUG = False def waitDongle(currentDialog, persoData): try: if persoData['client'] != None: try: persoData['client'].dongle.close() except Exception: pass dongle = getDongle(BTCHIP_DEBUG) persoData['client'] = btchip(dongle) persoData['client'].getFirmwareVersion()['version'].split(".") return True except BTChipException as e: if e.sw == 0x6faa: QMessageBox.information(currentDialog, "BTChip Setup", "Please unplug the dongle and plug it again", "OK") return False if QMessageBox.question(currentDialog, "BTChip setup", "BTChip dongle not found. It might be in the wrong mode. Try unplugging und plugging it back in again, then press 'OK'", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.Yes: return False else: raise Exception("Aborted by user") except Exception as e: if QMessageBox.question(currentDialog, "BTChip setup", "BTChip dongle not found. It might be in the wrong mode. Try unplugging und plugging it back in again, then press 'OK'", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.Yes: return False else: raise Exception("Aborted by user") class StartBTChipPersoDialog(QtGui.QDialog): def __init__(self): QDialog.__init__(self, None) self.ui = ui.personalization00start.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) def processNext(self): persoData = {} persoData['currencyCode'] = 0x00 persoData['currencyCodeP2SH'] = 0x05 persoData['client'] = None dialog = SeedDialog(persoData, self) persoData['main'] = self dialog.exec_() pass def processCancel(self): self.reject() class SeedDialog(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization01seed.Ui_Dialog() self.ui.setupUi(self) self.ui.seed.setEnabled(False) self.ui.RestoreWalletButton.toggled.connect(self.restoreWalletToggled) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) if MNEMONIC: self.mnemonic = Mnemonic('english') self.ui.mnemonicNotAvailableLabel.hide() def restoreWalletToggled(self, toggled): self.ui.seed.setEnabled(toggled) def processNext(self): self.persoData['seed'] = None if self.ui.RestoreWalletButton.isChecked(): # Check if it's an hexa string seedText = str(self.ui.seed.text()) if len(seedText) == 0: QMessageBox.warning(self, "Error", "Please enter a seed", "OK") return if seedText[-1] == 'X': seedText = seedText[0:-1] try: self.persoData['seed'] = seedText.decode('hex') except Exception: pass if self.persoData['seed'] == None: if not MNEMONIC: QMessageBox.warning(self, "Error", "Mnemonic API not available. Please install https://github.com/trezor/python-mnemonic", "OK") return if not self.mnemonic.check(seedText): QMessageBox.warning(self, "Error", "Invalid mnemonic", "OK") return self.persoData['seed'] = Mnemonic.to_seed(seedText) else: if (len(self.persoData['seed']) < 32) or (len(self.persoData['seed']) > 64): QMessageBox.warning(self, "Error", "Invalid seed length", "OK") return dialog = SecurityDialog(self.persoData, self) self.hide() dialog.exec_() def processCancel(self): self.reject() self.persoData['main'].reject() class SecurityDialog(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization02security.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) def processNext(self): if (self.ui.pin1.text() != self.ui.pin2.text()): self.ui.pin1.setText("") self.ui.pin2.setText("") QMessageBox.warning(self, "Error", "PINs are not matching", "OK") return if (len(self.ui.pin1.text()) < 4): QMessageBox.warning(self, "Error", "PIN must be at least 4 characteres long", "OK") return if (len(self.ui.pin1.text()) > 32): QMessageBox.warning(self, "Error", "PIN is too long", "OK") return self.persoData['pin'] = str(self.ui.pin1.text()) self.persoData['hardened'] = self.ui.HardenedButton.isChecked() dialog = ConfigDialog(self.persoData, self) self.hide() dialog.exec_() def processCancel(self): self.reject() self.persoData['main'].reject() class ConfigDialog(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization03config.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) def processNext(self): if (self.ui.qwertyButton.isChecked()): self.persoData['keyboard'] = btchip.QWERTY_KEYMAP elif (self.ui.qwertzButton.isChecked()): self.persoData['keyboard'] = btchip.QWERTZ_KEYMAP elif (self.ui.azertyButton.isChecked()): self.persoData['keyboard'] = btchip.AZERTY_KEYMAP try: while not waitDongle(self, self.persoData): pass except Exception as e: self.reject() self.persoData['main'].reject() mode = btchip.OPERATION_MODE_WALLET if not self.persoData['hardened']: mode = mode | btchip.OPERATION_MODE_SERVER try: self.persoData['client'].setup(mode, btchip.FEATURE_RFC6979, self.persoData['currencyCode'], self.persoData['currencyCodeP2SH'], self.persoData['pin'], None, self.persoData['keyboard'], self.persoData['seed']) except BTChipException as e: if e.sw == 0x6985: QMessageBox.warning(self, "Error", "Dongle is already set up. Please insert a different one", "OK") return except Exception as e: QMessageBox.warning(self, "Error", "Error performing setup", "OK") return if self.persoData['seed'] is None: dialog = SeedBackupStart(self.persoData, self) self.hide() dialog.exec_() else: dialog = FinalizeDialog(self.persoData, self) self.hide() dialog.exec_() def processCancel(self): self.reject() self.persoData['main'].reject() class FinalizeDialog(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization04finalize.Ui_Dialog() self.ui.setupUi(self) self.ui.FinishButton.clicked.connect(self.finish) try: while not waitDongle(self, self.persoData): pass except Exception as e: self.reject() self.persoData['main'].reject() attempts = self.persoData['client'].getVerifyPinRemainingAttempts() self.ui.remainingAttemptsLabel.setText("Remaining attempts " + str(attempts)) def finish(self): if (len(self.ui.pin1.text()) < 4): QMessageBox.warning(self, "Error", "PIN must be at least 4 characteres long", "OK") return if (len(self.ui.pin1.text()) > 32): QMessageBox.warning(self, "Error", "PIN is too long", "OK") return try: self.persoData['client'].verifyPin(str(self.ui.pin1.text())) except BTChipException as e: if ((e.sw == 0x63c0) or (e.sw == 0x6985)): QMessageBox.warning(self, "Error", "Invalid PIN - dongle has been reset. Please personalize again", "OK") self.reject() self.persoData['main'].reject() if ((e.sw & 0xfff0) == 0x63c0): attempts = e.sw - 0x63c0 self.ui.remainingAttemptsLabel.setText("Remaining attempts " + str(attempts)) QMessageBox.warning(self, "Error", "Invalid PIN - please unplug the dongle and plug it again before retrying", "OK") try: while not waitDongle(self, self.persoData): pass except Exception as e: self.reject() self.persoData['main'].reject() return except Exception as e: QMessageBox.warning(self, "Error", "Unexpected error verifying PIN - aborting", "OK") self.reject() self.persoData['main'].reject() return if not self.persoData['hardened']: try: self.persoData['client'].setOperationMode(btchip.OPERATION_MODE_SERVER) except Exception: QMessageBox.warning(self, "Error", "Error switching to non hardened mode", "OK") self.reject() self.persoData['main'].reject() return QMessageBox.information(self, "BTChip Setup", "Setup completed. Please unplug the dongle and plug it again before use", "OK") self.accept() self.persoData['main'].accept() class SeedBackupStart(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalizationseedbackup01.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) def processNext(self): dialog = SeedBackupUnplug(self.persoData, self) self.hide() dialog.exec_() class SeedBackupUnplug(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalizationseedbackup02.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) def processNext(self): dialog = SeedBackupInstructions(self.persoData, self) self.hide() dialog.exec_() class SeedBackupInstructions(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalizationseedbackup03.Ui_Dialog() self.ui.setupUi(self) self.ui.NextButton.clicked.connect(self.processNext) def processNext(self): dialog = SeedBackupVerify(self.persoData, self) self.hide() dialog.exec_() class SeedBackupVerify(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalizationseedbackup04.Ui_Dialog() self.ui.setupUi(self) self.ui.seedOkButton.clicked.connect(self.seedOK) self.ui.seedKoButton.clicked.connect(self.seedKO) def seedOK(self): dialog = FinalizeDialog(self.persoData, self) self.hide() dialog.exec_() def seedKO(self): finished = False while not finished: try: while not waitDongle(self, self.persoData): pass except Exception as e: pass try: self.persoData['client'].verifyPin("0") except BTChipException as e: if e.sw == 0x63c0: QMessageBox.information(self, "BTChip Setup", "Dongle is reset and can be repersonalized", "OK") finished = True pass if e.sw == 0x6faa: QMessageBox.information(self, "BTChip Setup", "Please unplug the dongle and plug it again", "OK") pass except Exception as e: pass self.reject() self.persoData['main'].reject() if __name__ == "__main__": app = QtGui.QApplication(sys.argv) dialog = StartBTChipPersoDialog() dialog.show() app.exec_() btchip-python-0.1.31/btchip/btchipUtils.py000066400000000000000000000065301373656177100205250ustar00rootroot00000000000000""" ******************************************************************************* * 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) btchip-python-0.1.31/btchip/ledgerWrapper.py000066400000000000000000000062301373656177100210330ustar00rootroot00000000000000""" ******************************************************************************* * 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) btchip-python-0.1.31/btchip/msqr.py000066400000000000000000000045001373656177100172100ustar00rootroot00000000000000# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ def modular_sqrt(a, p): """ Find a quadratic residue (mod p) of 'a'. p must be an odd prime. Solve the congruence of the form: x^2 = a (mod p) And returns x. Note that p - x is also a root. 0 is returned is no square root exists for these a and p. The Tonelli-Shanks algorithm is used (except for some simple cases in which the solution is known from an identity). This algorithm runs in polynomial time (unless the generalized Riemann hypothesis is false). """ # Simple cases # if legendre_symbol(a, p) != 1: return 0 elif a == 0: return 0 elif p == 2: return p elif p % 4 == 3: return pow(a, (p + 1) / 4, p) # Partition p-1 to s * 2^e for an odd s (i.e. # reduce all the powers of 2 from p-1) # s = p - 1 e = 0 while s % 2 == 0: s /= 2 e += 1 # Find some 'n' with a legendre symbol n|p = -1. # Shouldn't take long. # n = 2 while legendre_symbol(n, p) != -1: n += 1 # Here be dragons! # Read the paper "Square roots from 1; 24, 51, # 10 to Dan Shanks" by Ezra Brown for more # information # # x is a guess of the square root that gets better # with each iteration. # b is the "fudge factor" - by how much we're off # with the guess. The invariant x^2 = ab (mod p) # is maintained throughout the loop. # g is used for successive powers of n to update # both a and b # r is the exponent - decreases with each update # x = pow(a, (s + 1) / 2, p) b = pow(a, s, p) g = pow(n, s, p) r = e while True: t = b m = 0 for m in xrange(r): if t == 1: break t = pow(t, 2, p) if m == 0: return x gs = pow(g, 2 ** (r - m - 1), p) g = (gs * gs) % p x = (x * gs) % p b = (b * g) % p r = m def legendre_symbol(a, p): """ Compute the Legendre symbol a|p using Euler's criterion. p is a prime, a is relatively prime to p (if p divides a, then a|p = 0) Returns 1 if a has a square root modulo p, -1 otherwise. """ ls = pow(a, (p - 1) / 2, p) return -1 if ls == p - 1 else ls btchip-python-0.1.31/btchip/ui/000077500000000000000000000000001373656177100162725ustar00rootroot00000000000000btchip-python-0.1.31/btchip/ui/__init__.py000066400000000000000000000015101373656177100204000ustar00rootroot00000000000000""" ******************************************************************************* * 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. ******************************************************************************** """ btchip-python-0.1.31/btchip/ui/personalization00start.py000066400000000000000000000055131373656177100233070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-00-start.ui' # # Created: Fri Sep 19 15:03:25 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 231) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(120, 20, 231, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(20, 60, 351, 61)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(310, 200, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.arningLabel = QtGui.QLabel(Dialog) self.arningLabel.setGeometry(QtCore.QRect(20, 120, 351, 81)) self.arningLabel.setWordWrap(True) self.arningLabel.setObjectName(_fromUtf8("arningLabel")) self.CancelButton = QtGui.QPushButton(Dialog) self.CancelButton.setGeometry(QtCore.QRect(20, 200, 75, 25)) self.CancelButton.setObjectName(_fromUtf8("CancelButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Your BTChip dongle is not set up - you\'ll be able to create a new wallet, or restore an existing one, and choose your security profile.", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) self.arningLabel.setText(QtGui.QApplication.translate("Dialog", "Sensitive information including your dongle PIN will be exchanged during this setup phase - it is recommended to execute it on a secure computer, disconnected from any network, especially if you restore a wallet backup.", None, QtGui.QApplication.UnicodeUTF8)) self.CancelButton.setText(QtGui.QApplication.translate("Dialog", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalization01seed.py000066400000000000000000000102361373656177100230710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-01-seed.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 300) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(50, 20, 311, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(20, 60, 351, 61)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.NewWalletButton = QtGui.QRadioButton(Dialog) self.NewWalletButton.setGeometry(QtCore.QRect(20, 130, 94, 21)) self.NewWalletButton.setChecked(True) self.NewWalletButton.setObjectName(_fromUtf8("NewWalletButton")) self.buttonGroup = QtGui.QButtonGroup(Dialog) self.buttonGroup.setObjectName(_fromUtf8("buttonGroup")) self.buttonGroup.addButton(self.NewWalletButton) self.RestoreWalletButton = QtGui.QRadioButton(Dialog) self.RestoreWalletButton.setGeometry(QtCore.QRect(20, 180, 171, 21)) self.RestoreWalletButton.setObjectName(_fromUtf8("RestoreWalletButton")) self.buttonGroup.addButton(self.RestoreWalletButton) self.seed = QtGui.QLineEdit(Dialog) self.seed.setEnabled(False) self.seed.setGeometry(QtCore.QRect(50, 210, 331, 21)) self.seed.setEchoMode(QtGui.QLineEdit.Normal) self.seed.setObjectName(_fromUtf8("seed")) self.CancelButton = QtGui.QPushButton(Dialog) self.CancelButton.setGeometry(QtCore.QRect(10, 270, 75, 25)) self.CancelButton.setObjectName(_fromUtf8("CancelButton")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(320, 270, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.mnemonicNotAvailableLabel = QtGui.QLabel(Dialog) self.mnemonicNotAvailableLabel.setGeometry(QtCore.QRect(130, 240, 171, 31)) font = QtGui.QFont() font.setItalic(True) self.mnemonicNotAvailableLabel.setFont(font) self.mnemonicNotAvailableLabel.setWordWrap(True) self.mnemonicNotAvailableLabel.setObjectName(_fromUtf8("mnemonicNotAvailableLabel")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup - seed", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - seed (1/3)", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Please select an option : either create a new wallet or restore an existing one", None, QtGui.QApplication.UnicodeUTF8)) self.NewWalletButton.setText(QtGui.QApplication.translate("Dialog", "New Wallet", None, QtGui.QApplication.UnicodeUTF8)) self.RestoreWalletButton.setText(QtGui.QApplication.translate("Dialog", "Restore wallet backup", None, QtGui.QApplication.UnicodeUTF8)) self.seed.setPlaceholderText(QtGui.QApplication.translate("Dialog", "Enter an hexadecimal seed or a BIP 39 mnemonic code", None, QtGui.QApplication.UnicodeUTF8)) self.CancelButton.setText(QtGui.QApplication.translate("Dialog", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) self.mnemonicNotAvailableLabel.setText(QtGui.QApplication.translate("Dialog", "Mnemonic API is not available", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalization02security.py000066400000000000000000000133641373656177100240260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-02-security.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 503) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(20, 20, 361, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(10, 60, 351, 61)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.HardenedButton = QtGui.QRadioButton(Dialog) self.HardenedButton.setGeometry(QtCore.QRect(20, 110, 81, 21)) self.HardenedButton.setChecked(True) self.HardenedButton.setObjectName(_fromUtf8("HardenedButton")) self.buttonGroup = QtGui.QButtonGroup(Dialog) self.buttonGroup.setObjectName(_fromUtf8("buttonGroup")) self.buttonGroup.addButton(self.HardenedButton) self.HardenedButton_2 = QtGui.QRadioButton(Dialog) self.HardenedButton_2.setGeometry(QtCore.QRect(20, 210, 81, 21)) self.HardenedButton_2.setObjectName(_fromUtf8("HardenedButton_2")) self.buttonGroup.addButton(self.HardenedButton_2) self.IntroLabel_2 = QtGui.QLabel(Dialog) self.IntroLabel_2.setGeometry(QtCore.QRect(50, 140, 351, 61)) self.IntroLabel_2.setWordWrap(True) self.IntroLabel_2.setObjectName(_fromUtf8("IntroLabel_2")) self.IntroLabel_3 = QtGui.QLabel(Dialog) self.IntroLabel_3.setGeometry(QtCore.QRect(50, 230, 351, 61)) self.IntroLabel_3.setWordWrap(True) self.IntroLabel_3.setObjectName(_fromUtf8("IntroLabel_3")) self.CancelButton = QtGui.QPushButton(Dialog) self.CancelButton.setGeometry(QtCore.QRect(10, 470, 75, 25)) self.CancelButton.setObjectName(_fromUtf8("CancelButton")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(310, 470, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.IntroLabel_4 = QtGui.QLabel(Dialog) self.IntroLabel_4.setGeometry(QtCore.QRect(10, 300, 351, 61)) self.IntroLabel_4.setWordWrap(True) self.IntroLabel_4.setObjectName(_fromUtf8("IntroLabel_4")) self.IntroLabel_5 = QtGui.QLabel(Dialog) self.IntroLabel_5.setGeometry(QtCore.QRect(20, 380, 161, 31)) self.IntroLabel_5.setWordWrap(True) self.IntroLabel_5.setObjectName(_fromUtf8("IntroLabel_5")) self.pin1 = QtGui.QLineEdit(Dialog) self.pin1.setGeometry(QtCore.QRect(210, 380, 161, 21)) self.pin1.setEchoMode(QtGui.QLineEdit.Password) self.pin1.setObjectName(_fromUtf8("pin1")) self.pin2 = QtGui.QLineEdit(Dialog) self.pin2.setGeometry(QtCore.QRect(210, 420, 161, 21)) self.pin2.setEchoMode(QtGui.QLineEdit.Password) self.pin2.setObjectName(_fromUtf8("pin2")) self.IntroLabel_6 = QtGui.QLabel(Dialog) self.IntroLabel_6.setGeometry(QtCore.QRect(20, 420, 171, 31)) self.IntroLabel_6.setWordWrap(True) self.IntroLabel_6.setObjectName(_fromUtf8("IntroLabel_6")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup - security", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - security (2/3)", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Please choose a security profile", None, QtGui.QApplication.UnicodeUTF8)) self.HardenedButton.setText(QtGui.QApplication.translate("Dialog", "Hardened", None, QtGui.QApplication.UnicodeUTF8)) self.HardenedButton_2.setText(QtGui.QApplication.translate("Dialog", "PIN only", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_2.setText(QtGui.QApplication.translate("Dialog", "You need to remove the dongle and insert it again to get a second factor validation of all operations. Recommended for expert users and to be fully protected against malwares.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_3.setText(QtGui.QApplication.translate("Dialog", "You only need to enter a PIN once when inserting the dongle. Transactions are not protected against malwares", None, QtGui.QApplication.UnicodeUTF8)) self.CancelButton.setText(QtGui.QApplication.translate("Dialog", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_4.setText(QtGui.QApplication.translate("Dialog", "Please choose a PIN associated to the BTChip dongle. The PIN protects the dongle in case it is stolen, and can be up to 32 characters. The dongle is wiped if a wrong PIN is entered 3 times in a row.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_5.setText(QtGui.QApplication.translate("Dialog", "Enter the new PIN : ", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_6.setText(QtGui.QApplication.translate("Dialog", "Repeat the new PIN :", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalization03config.py000066400000000000000000000067451373656177100234320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-03-config.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 243) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(30, 10, 361, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(20, 50, 351, 61)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.qwertyButton = QtGui.QRadioButton(Dialog) self.qwertyButton.setGeometry(QtCore.QRect(50, 110, 94, 21)) self.qwertyButton.setChecked(True) self.qwertyButton.setObjectName(_fromUtf8("qwertyButton")) self.keyboardGroup = QtGui.QButtonGroup(Dialog) self.keyboardGroup.setObjectName(_fromUtf8("keyboardGroup")) self.keyboardGroup.addButton(self.qwertyButton) self.qwertzButton = QtGui.QRadioButton(Dialog) self.qwertzButton.setGeometry(QtCore.QRect(50, 140, 94, 21)) self.qwertzButton.setObjectName(_fromUtf8("qwertzButton")) self.keyboardGroup.addButton(self.qwertzButton) self.azertyButton = QtGui.QRadioButton(Dialog) self.azertyButton.setGeometry(QtCore.QRect(50, 170, 94, 21)) self.azertyButton.setObjectName(_fromUtf8("azertyButton")) self.keyboardGroup.addButton(self.azertyButton) self.CancelButton = QtGui.QPushButton(Dialog) self.CancelButton.setGeometry(QtCore.QRect(10, 210, 75, 25)) self.CancelButton.setObjectName(_fromUtf8("CancelButton")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(320, 210, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - config (3/3)", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Please select your keyboard type to type the second factor confirmation", None, QtGui.QApplication.UnicodeUTF8)) self.qwertyButton.setText(QtGui.QApplication.translate("Dialog", "QWERTY", None, QtGui.QApplication.UnicodeUTF8)) self.qwertzButton.setText(QtGui.QApplication.translate("Dialog", "QWERTZ", None, QtGui.QApplication.UnicodeUTF8)) self.azertyButton.setText(QtGui.QApplication.translate("Dialog", "AZERTY", None, QtGui.QApplication.UnicodeUTF8)) self.CancelButton.setText(QtGui.QApplication.translate("Dialog", "Cancel", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalization04finalize.py000066400000000000000000000060571373656177100237630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-04-finalize.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 267) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(20, 20, 361, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.FinishButton = QtGui.QPushButton(Dialog) self.FinishButton.setGeometry(QtCore.QRect(320, 230, 75, 25)) self.FinishButton.setObjectName(_fromUtf8("FinishButton")) self.IntroLabel_4 = QtGui.QLabel(Dialog) self.IntroLabel_4.setGeometry(QtCore.QRect(10, 70, 351, 61)) self.IntroLabel_4.setWordWrap(True) self.IntroLabel_4.setObjectName(_fromUtf8("IntroLabel_4")) self.IntroLabel_5 = QtGui.QLabel(Dialog) self.IntroLabel_5.setGeometry(QtCore.QRect(50, 140, 121, 21)) self.IntroLabel_5.setWordWrap(True) self.IntroLabel_5.setObjectName(_fromUtf8("IntroLabel_5")) self.pin1 = QtGui.QLineEdit(Dialog) self.pin1.setGeometry(QtCore.QRect(200, 140, 181, 21)) self.pin1.setEchoMode(QtGui.QLineEdit.Password) self.pin1.setObjectName(_fromUtf8("pin1")) self.remainingAttemptsLabel = QtGui.QLabel(Dialog) self.remainingAttemptsLabel.setGeometry(QtCore.QRect(120, 170, 171, 31)) font = QtGui.QFont() font.setItalic(True) self.remainingAttemptsLabel.setFont(font) self.remainingAttemptsLabel.setWordWrap(True) self.remainingAttemptsLabel.setObjectName(_fromUtf8("remainingAttemptsLabel")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup - security", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - completed", None, QtGui.QApplication.UnicodeUTF8)) self.FinishButton.setText(QtGui.QApplication.translate("Dialog", "Finish", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_4.setText(QtGui.QApplication.translate("Dialog", "BTChip setup is completed. Please enter your PIN to validate it then press Finish", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_5.setText(QtGui.QApplication.translate("Dialog", "BTChip PIN :", None, QtGui.QApplication.UnicodeUTF8)) self.remainingAttemptsLabel.setText(QtGui.QApplication.translate("Dialog", "Remaining attempts", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalizationseedbackup01.py000066400000000000000000000072321373656177100242610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-seedbackup-01.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 300) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(30, 20, 351, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(320, 270, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(10, 100, 351, 31)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.IntroLabel_2 = QtGui.QLabel(Dialog) self.IntroLabel_2.setGeometry(QtCore.QRect(10, 140, 351, 31)) self.IntroLabel_2.setWordWrap(True) self.IntroLabel_2.setObjectName(_fromUtf8("IntroLabel_2")) self.IntroLabel_3 = QtGui.QLabel(Dialog) self.IntroLabel_3.setGeometry(QtCore.QRect(10, 180, 351, 41)) self.IntroLabel_3.setWordWrap(True) self.IntroLabel_3.setObjectName(_fromUtf8("IntroLabel_3")) self.TitleLabel_2 = QtGui.QLabel(Dialog) self.TitleLabel_2.setGeometry(QtCore.QRect(90, 60, 251, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel_2.setFont(font) self.TitleLabel_2.setObjectName(_fromUtf8("TitleLabel_2")) self.IntroLabel_4 = QtGui.QLabel(Dialog) self.IntroLabel_4.setGeometry(QtCore.QRect(10, 220, 351, 41)) self.IntroLabel_4.setWordWrap(True) self.IntroLabel_4.setObjectName(_fromUtf8("IntroLabel_4")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - seed backup", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "A new seed has been generated for your wallet.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_2.setText(QtGui.QApplication.translate("Dialog", "You must backup this seed and keep it out of reach of hackers (typically by keeping it on paper).", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_3.setText(QtGui.QApplication.translate("Dialog", "You can use this seed to restore your dongle if you lose it or access your funds with any other compatible wallet.", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel_2.setText(QtGui.QApplication.translate("Dialog", "READ CAREFULLY", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_4.setText(QtGui.QApplication.translate("Dialog", "Press Next to start the backuping process.", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalizationseedbackup02.py000066400000000000000000000036331373656177100242630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-seedbackup-02.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 300) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(30, 20, 351, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(20, 70, 351, 31)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(320, 270, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - seed backup", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Please disconnect the dongle then press Next", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalizationseedbackup03.py000066400000000000000000000114311373656177100242570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-seedbackup-03.ui' # # Created: Thu Aug 28 22:26:22 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 513) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(20, 10, 351, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(20, 50, 351, 61)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.IntroLabel_2 = QtGui.QLabel(Dialog) self.IntroLabel_2.setGeometry(QtCore.QRect(20, 120, 351, 31)) self.IntroLabel_2.setWordWrap(True) self.IntroLabel_2.setObjectName(_fromUtf8("IntroLabel_2")) self.IntroLabel_3 = QtGui.QLabel(Dialog) self.IntroLabel_3.setGeometry(QtCore.QRect(20, 160, 351, 51)) self.IntroLabel_3.setWordWrap(True) self.IntroLabel_3.setObjectName(_fromUtf8("IntroLabel_3")) self.IntroLabel_4 = QtGui.QLabel(Dialog) self.IntroLabel_4.setGeometry(QtCore.QRect(20, 220, 351, 51)) self.IntroLabel_4.setWordWrap(True) self.IntroLabel_4.setObjectName(_fromUtf8("IntroLabel_4")) self.IntroLabel_5 = QtGui.QLabel(Dialog) self.IntroLabel_5.setGeometry(QtCore.QRect(20, 280, 351, 71)) self.IntroLabel_5.setWordWrap(True) self.IntroLabel_5.setObjectName(_fromUtf8("IntroLabel_5")) self.IntroLabel_6 = QtGui.QLabel(Dialog) self.IntroLabel_6.setGeometry(QtCore.QRect(20, 350, 351, 51)) self.IntroLabel_6.setWordWrap(True) self.IntroLabel_6.setObjectName(_fromUtf8("IntroLabel_6")) self.IntroLabel_7 = QtGui.QLabel(Dialog) self.IntroLabel_7.setGeometry(QtCore.QRect(20, 410, 351, 51)) self.IntroLabel_7.setWordWrap(True) self.IntroLabel_7.setObjectName(_fromUtf8("IntroLabel_7")) self.NextButton = QtGui.QPushButton(Dialog) self.NextButton.setGeometry(QtCore.QRect(310, 480, 75, 25)) self.NextButton.setObjectName(_fromUtf8("NextButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - seed backup", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "If you do not trust this computer, perform the following steps on a trusted one or a different device. Anything supporting keyboard input will work (smartphone, TV box ...)", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_2.setText(QtGui.QApplication.translate("Dialog", "Open a text editor, set the focus on the text editor, then insert the dongle", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_3.setText(QtGui.QApplication.translate("Dialog", "After a very short time, the dongle will type the seed as hexadecimal (0..9 A..F) characters, starting with \"seed\" and ending with \"X\"", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_4.setText(QtGui.QApplication.translate("Dialog", "If you perform those steps on Windows, a new device driver will be loaded the first time and the seed will not be typed. This is normal.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_5.setText(QtGui.QApplication.translate("Dialog", "If you perform those steps on Mac, you\'ll get a popup asking you to select a keyboard type the first time and the seed will not be typed. This is normal, just close the popup.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_6.setText(QtGui.QApplication.translate("Dialog", "If you did not see the seed for any reason, keep the focus on the text editor, unplug and plug the dongle again twice.", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel_7.setText(QtGui.QApplication.translate("Dialog", "Then press Next once you wrote the seed to a safe medium (i.e. paper) and unplugged the dongle", None, QtGui.QApplication.UnicodeUTF8)) self.NextButton.setText(QtGui.QApplication.translate("Dialog", "Next", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/btchip/ui/personalizationseedbackup04.py000066400000000000000000000045601373656177100242650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'personalization-seedbackup-04.ui' # # Created: Thu Aug 28 22:26:23 2014 # by: PyQt4 UI code generator 4.9.1 # # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(554, 190) self.TitleLabel = QtGui.QLabel(Dialog) self.TitleLabel.setGeometry(QtCore.QRect(30, 10, 351, 31)) font = QtGui.QFont() font.setPointSize(20) font.setBold(True) font.setItalic(True) font.setWeight(75) self.TitleLabel.setFont(font) self.TitleLabel.setObjectName(_fromUtf8("TitleLabel")) self.IntroLabel = QtGui.QLabel(Dialog) self.IntroLabel.setGeometry(QtCore.QRect(10, 50, 351, 51)) self.IntroLabel.setWordWrap(True) self.IntroLabel.setObjectName(_fromUtf8("IntroLabel")) self.seedOkButton = QtGui.QPushButton(Dialog) self.seedOkButton.setGeometry(QtCore.QRect(20, 140, 501, 25)) self.seedOkButton.setObjectName(_fromUtf8("seedOkButton")) self.seedKoButton = QtGui.QPushButton(Dialog) self.seedKoButton.setGeometry(QtCore.QRect(20, 110, 501, 25)) self.seedKoButton.setObjectName(_fromUtf8("seedKoButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "BTChip setup", None, QtGui.QApplication.UnicodeUTF8)) self.TitleLabel.setText(QtGui.QApplication.translate("Dialog", "BTChip setup - seed backup", None, QtGui.QApplication.UnicodeUTF8)) self.IntroLabel.setText(QtGui.QApplication.translate("Dialog", "Did you see the seed correctly displayed and did you backup it properly ?", None, QtGui.QApplication.UnicodeUTF8)) self.seedOkButton.setText(QtGui.QApplication.translate("Dialog", "Yes, the seed is backed up properly and kept in a safe place, move on", None, QtGui.QApplication.UnicodeUTF8)) self.seedKoButton.setText(QtGui.QApplication.translate("Dialog", "No, I didn\'t see the seed. Wipe the dongle and start over", None, QtGui.QApplication.UnicodeUTF8)) btchip-python-0.1.31/samples/000077500000000000000000000000001373656177100160505ustar00rootroot00000000000000btchip-python-0.1.31/samples/getFirmwareVersion.py000066400000000000000000000017111373656177100222440ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * import sys dongle = getDongle(True) app = btchip(dongle) print(app.getFirmwareVersion()['version']) btchip-python-0.1.31/samples/runScript.py000066400000000000000000000031301373656177100204100ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * import sys import binascii if len(sys.argv) < 2: print("Usage : %s script to run" % sys.argv[0]) sys.exit(2) dongle = getDongle(True) scriptFile = open(sys.argv[1], "r") line = scriptFile.readline() while line: if (len(line) == 0) or (line[0] == '#') or (line.find('[') >= 0) or (line.find(']') >= 0): line = scriptFile.readline() continue line = line.replace('\"', '') line = line.replace(',', '') cancelResponse = (line[0] == '!') timeout = 10000 if cancelResponse: line = line[1:] timeout = 1 try: line = line.strip() if len(line) == 0: continue dongle.exchange(bytearray(binascii.unhexlify(line)), timeout) except Exception: if cancelResponse: pass else: raise line = scriptFile.readline() scriptFile.close() btchip-python-0.1.31/setup.py000066400000000000000000000016361373656177100161240ustar00rootroot00000000000000#from distribute_setup import use_setuptools #use_setuptools() from setuptools import setup, find_packages from os.path import dirname, join here = dirname(__file__) import btchip setup( name='btchip-python', version=btchip.__version__, author='BTChip', author_email='hello@ledger.fr', description='Python library to communicate with Ledger Nano dongle', long_description=open(join(here, 'README.md')).read(), url='https://github.com/LedgerHQ/btchip-python', packages=find_packages(), install_requires=['hidapi>=0.7.99'], extras_require = { 'smartcard': [ 'python-pyscard>=1.6.12-4build1', 'ecdsa>=0.9' ] }, include_package_data=True, zip_safe=False, classifiers=[ 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX :: Linux', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X' ] ) btchip-python-0.1.31/tests/000077500000000000000000000000001373656177100155465ustar00rootroot00000000000000btchip-python-0.1.31/tests/coinkite-cosigning/000077500000000000000000000000001373656177100213315ustar00rootroot00000000000000btchip-python-0.1.31/tests/coinkite-cosigning/testGetPublicKeyDev.js000066400000000000000000000070341373656177100255610ustar00rootroot00000000000000""" ******************************************************************************* * 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. ******************************************************************************** """ # Coinkite co-signer provisioning # To be run with the dongle in developer mode, PIN verified import hashlib from btchip.btchip import * from btchip.btchipUtils import * from base64 import b64encode # Replace with your own seed (preferably import it and store it), key path, and Testnet flag SEED = bytearray("fe721b95503a18a14d93914e02ff153f924737c336b01f98f2ff39395f630187".decode('hex')) KEYPATH = "0'/2/0" TESTNET = True # From Electrum __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58base = len(__b58chars) def b58encode(v): """ encode v, which is a string of bytes, to base58.""" long_value = 0L for (i, c) in enumerate(v[::-1]): long_value += (256**i) * ord(c) result = '' while long_value >= __b58base: div, mod = divmod(long_value, __b58base) result = __b58chars[mod] + result long_value = div result = __b58chars[long_value] + result # Bitcoin does a little leading-zero-compression: # leading 0-bytes in the input become leading-1s nPad = 0 for c in v: if c == '\0': nPad += 1 else: break return (__b58chars[0]*nPad) + result def EncodeBase58Check(vchIn): hash = Hash(vchIn) return b58encode(vchIn + hash[0:4]) def sha256(x): return hashlib.sha256(x).digest() def Hash(x): if type(x) is unicode: x=x.encode('utf-8') return sha256(sha256(x)) def i4b(self, x): return pack('>I', x) # /from Electrum def getXpub(publicKeyData, testnet=False): header = ("043587CF" if testnet else "0488B21E") result = header.decode('hex') + chr(publicKeyData['depth']) + str(publicKeyData['parentFingerprint']) + str(publicKeyData['childNumber']) + str(publicKeyData['chainCode']) + str(compress_public_key(publicKeyData['publicKey'])) return EncodeBase58Check(result) def signMessage(encodedPrivateKey, data): messageData = bytearray("\x18Bitcoin Signed Message:\n") writeVarint(len(data), messageData) messageData.extend(data) messageHash = Hash(messageData) signature = app.signImmediate(encodedPrivateKey, messageHash) # Parse the ASN.1 signature rLength = signature[3] r = signature[4 : 4 + rLength] sLength = signature[4 + rLength + 1] s = signature[4 + rLength + 2:] if rLength == 33: r = r[1:] if sLength == 33: s = s[1:] r = str(r) s = str(s) # And convert it return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) dongle = getDongle(True) app = btchip(dongle) seed = app.importPrivateKey(SEED, TESTNET) privateKey = app.deriveBip32Key(seed, KEYPATH) publicKeyData = app.getPublicKey(privateKey) print getXpub(publicKeyData, TESTNET) print signMessage(privateKey, "Coinkite") dongle.close() btchip-python-0.1.31/tests/coinkite-cosigning/testSignDev.js000066400000000000000000000122301373656177100241240ustar00rootroot00000000000000""" ******************************************************************************* * 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. ******************************************************************************** """ # Coinkite co-signer signing # To be run with the dongle in developer mode, PIN verified # Pass the JSON request to sign as parameter # TODO : verify Coinkite signature in the request import hashlib import sys import json from btchip.btchip import * from btchip.btchipUtils import * from base64 import b64encode # Replace with your own seed (preferably import it and store it), key path, and Testnet flag SEED = bytearray("fe721b95503a18a14d93914e02ff153f924737c336b01f98f2ff39395f630187".decode('hex')) KEYPATH = "0'/2/0" TESTNET = True # From Electrum __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58base = len(__b58chars) def b58encode(v): """ encode v, which is a string of bytes, to base58.""" long_value = 0L for (i, c) in enumerate(v[::-1]): long_value += (256**i) * ord(c) result = '' while long_value >= __b58base: div, mod = divmod(long_value, __b58base) result = __b58chars[mod] + result long_value = div result = __b58chars[long_value] + result # Bitcoin does a little leading-zero-compression: # leading 0-bytes in the input become leading-1s nPad = 0 for c in v: if c == '\0': nPad += 1 else: break return (__b58chars[0]*nPad) + result def EncodeBase58Check(vchIn): hash = Hash(vchIn) return b58encode(vchIn + hash[0:4]) def sha256(x): return hashlib.sha256(x).digest() def hash_160(public_key): try: md = hashlib.new('ripemd160') md.update(sha256(public_key)) return md.digest() except Exception: import ripemd md = ripemd.new(sha256(public_key)) return md.digest() def Hash(x): if type(x) is unicode: x=x.encode('utf-8') return sha256(sha256(x)) def i4b(self, x): return pack('>I', x) # /from Electrum def getXpub(publicKeyData, testnet=False): header = ("043587CF" if testnet else "0488B21E") result = header.decode('hex') + chr(publicKeyData['depth']) + str(publicKeyData['parentFingerprint']) + str(publicKeyData['childNumber']) + str(publicKeyData['chainCode']) + str(compress_public_key(publicKeyData['publicKey'])) return EncodeBase58Check(result) def getPub(publicKeyData, testnet=False): header = ("6F" if testnet else "00") keyData = hash_160(str(compress_public_key(publicKeyData))) return EncodeBase58Check(header.decode('hex') + keyData) def signMessage(encodedPrivateKey, data): messageData = bytearray("\x18Bitcoin Signed Message:\n") writeVarint(len(data), messageData) messageData.extend(data) messageHash = Hash(messageData) signature = app.signImmediate(encodedPrivateKey, messageHash) # Parse the ASN.1 signature rLength = signature[3] r = signature[4 : 4 + rLength] sLength = signature[4 + rLength + 1] s = signature[4 + rLength + 2:] if rLength == 33: r = r[1:] if sLength == 33: s = s[1:] r = str(r) s = str(s) # And convert it return b64encode(chr(27 + 4 + (signature[0] & 0x01)) + r + s) f = open(sys.argv[1], 'r') signData = json.load(f) requestData = json.loads(signData['contents']) result = {} result['cosigner'] = requestData['cosigner'] result['request'] = requestData['request'] result['signatures'] = [] dongle = getDongle(True) app = btchip(dongle) seed = app.importPrivateKey(SEED, TESTNET) privateKey = app.deriveBip32Key(seed, KEYPATH) publicKeyData = app.getPublicKey(privateKey) wallets = {} for key in requestData['req_keys'].keys(): privateKeyDiv = app.deriveBip32Key(privateKey, key) publicKeyDataDiv = app.getPublicKey(privateKeyDiv) if getPub(publicKeyDataDiv['publicKey'], TESTNET) == requestData['req_keys'][key][0]: wallets[key] = privateKeyDiv else: raise "Invalid wallet, could not match key" for signInput in requestData['inputs']: sigHash = signInput[1].decode('hex') signature = "\x30" + str(app.signImmediate(wallets[signInput[0]], sigHash)[1:]) signature = signature + "\x01" result['signatures'].append([signature.encode('hex'), sigHash.encode('hex'), signInput[0]]) body = {} body['_humans'] = "Upload this set of signatures to Coinkite." body['content'] = json.dumps(result) body['signature'] = signMessage(privateKey, body['content']) body['signed_by'] = getPub(publicKeyData['publicKey'], False) # Bug, should use network print json.dumps(body) dongle.close() btchip-python-0.1.31/tests/testConnectivity.py000066400000000000000000000022031373656177100214730ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * #from btchip.btchipUtils import * # Run on any dongle to test connectivity. dongle = getDongle(True) app = btchip(dongle) print('btchip firmware version:') print(app.getFirmwareVersion()) print('some random number from the dongle:') print(map(hex, app.getRandom(20))) dongle.close() btchip-python-0.1.31/tests/testMessageSignature.py000066400000000000000000000046331373656177100222740ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * from btchip.btchipUtils import * # Run on non configured dongle or dongle configured with test seed below SEED = bytearray("1762F9A3007DBC825D0DD9958B04880284E88F10C57CF569BB3DADF7B1027F2D".decode('hex')) MESSAGE = "Campagne de Sarkozy : une double comptabilite chez Bygmalion" SECONDFACTOR_1 = "Powercycle then confirm signature of .Campagne de Sarkozy : une double comptabilite chez Bygmalion. for address 17JusYNVXLPm3hBPzzRQkARYDMUBgRUMVc with PIN" SIGNATURE = bytearray("30450221009a0d28391c0535aec1077bbb86614c8f3c384a3e9aa1a124bfb9ce9649196b7e02200efa1adc010a7bdde4784ee98441e402f93b3c50a2760cb09dda07501e02c81f".decode('hex')) # Optional setup dongle = getDongle(True) app = btchip(dongle) try: app.setup(btchip.OPERATION_MODE_WALLET, btchip.FEATURE_RFC6979, 0x00, 0x05, "1234", None, btchip.QWERTY_KEYMAP, SEED) except Exception: pass # Authenticate app.verifyPin("1234") # Start signing app.signMessagePrepare("0'/0/0", MESSAGE) dongle.close() # Wait for the second factor confirmation # Done on the same application for test purposes, this is typically done in another window # or another computer for bigger transactions response = raw_input("Powercycle the dongle to get the second factor and powercycle again : ") if not response.startswith(SECONDFACTOR_1): raise BTChipException("Invalid second factor") # Get a reference to the dongle again, as it was disconnected dongle = getDongle(True) app = btchip(dongle) # Compute the signature signature = app.signMessageSign(response[len(response) - 4:]) if signature <> SIGNATURE: raise BTChipException("Invalid signature") btchip-python-0.1.31/tests/testMultisigArmory.py000066400000000000000000000230721373656177100220130ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * from btchip.btchipUtils import * import json """ Signs a TX generated by Armory. That TX: { 'inputs': [{ 'p2shscript': '52210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253ae', 'supporttxhash_be': '0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300', 'sequence': 4294967295, 'keys': [{ 'dersighex': '', 'pubkeyhex': '0269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c6283', 'wltlochex': '' }, { 'dersighex': '', 'pubkeyhex': '02f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb5', 'wltlochex': '' }, { 'dersighex': '', 'pubkeyhex': '037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b9102', 'wltlochex': '' }], 'contriblabel': u '', 'supporttxhash_le': '008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c', 'contribid': 'JLBercZk', 'version': 1, 'inputvalue': 46000000, 'outpoint': '008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c00000000', 'magicbytes': '0b110907', 'supporttx': '01000000013e9fe12917d854a0e093b982eaa46990289e2262f2db9fc1bd3f13718f3c806e010000006b483045022100af668e482e3ed363f51b36ddabad7cdf20d177104c92b8676a5b14f51107179602206c4ecd67544c74c6689ca453e2157d0c0b8a4608d85956429d2615275a51c66f01210374db359a004626daf2fcf10b8601f5f39438848a6733c768e88ce0ad398ae79dffffffff0280e7bd020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c3876c8fc846000000001976a914af58f09cf65b213bb9bd181a94e133b4ad4d6b2788ac00000000', 'numkeys': 3, 'supporttxhash': '0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300', 'supporttxoutindex': 0 }], 'fee': 10000, 'locktimeint': 0, 'outputs': [{ 'txoutvalue': 10000000, 'authdata': '', 'contriblabel': '', 'p2shscript': '', 'scripttypeint': 4, 'isp2sh': True, 'txoutscript': 'a914c0c3b6ada732c797881d00de6c350eec96e3d22287', 'authmethod': 'NONE', 'hasaddrstr': True, 'contribid': '', 'version': 1, 'ismultisig': False, 'magicbytes': '0b110907', 'addrstr': '2NApUBXv4NB8pm834pHUajiUL6rvFaaj6N8', 'scripttypestr': 'Standard (P2SH)', 'wltlocator': '' }, { 'txoutvalue': 35990000, 'authdata': '', 'contriblabel': '', 'p2shscript': '', 'scripttypeint': 4, 'isp2sh': True, 'txoutscript': 'a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387', 'authmethod': 'NONE', 'hasaddrstr': True, 'contribid': '', 'version': 1, 'ismultisig': False, 'magicbytes': '0b110907', 'addrstr': '2NDuYxRrmAs2fRcMj4ew2F41aFp2PN9yiV1', 'scripttypestr': 'Standard (P2SH)', 'wltlocator': '' }], 'sumoutputs': 45990000, 'suminputs': 46000000, 'version': 1, 'numoutputs': 2, 'magicbytes': '0b110907', 'locktimedate': '', 'locktimeblock': 0, 'id': '8jkccikU', 'numinputs': 1 } Input comes from vout[0] of 0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300. TX I want to generate is 0.10 to 2NApUBXv4NB8pm834pHUajiUL6rvFaaj6N8 The multisig address 2NDuYxRrmAs2fRcMj4ew2F41aFp2PN9yiV1 contains 0.46 BTC, and is generated using the public keys 0'/0/0, 0'/0/1, and 0'/0/2 from the seed below. """ # Run on non configured dongle or dongle configured with test seed below SEED = bytearray("1762F9A3007DBC825D0DD9958B04880284C88A10C57CF569BB3DADF7B1027F2D".decode('hex')) # Armory supporttx UTX = bytearray("01000000013e9fe12917d854a0e093b982eaa46990289e2262f2db9fc1bd3f13718f3c806e010000006b483045022100af668e482e3ed363f51b36ddabad7cdf20d177104c92b8676a5b14f51107179602206c4ecd67544c74c6689ca453e2157d0c0b8a4608d85956429d2615275a51c66f01210374db359a004626daf2fcf10b8601f5f39438848a6733c768e88ce0ad398ae79dffffffff0280e7bd020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c3876c8fc846000000001976a914af58f09cf65b213bb9bd181a94e133b4ad4d6b2788ac00000000".decode('hex')) UTXO_INDEX = 0 OUTPUT = bytearray("02809698000000000017a914c0c3b6ada732c797881d00de6c350eec96e3d22287f02925020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387".decode('hex')) # Armory p2shscript REDEEMSCRIPT = bytearray("52210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253ae".decode('hex')) SIGNATURE_0 = bytearray("3044022056cb1b781fd04cfe6c04756ad56d02e5512f3fe7f411bc22d1594da5c815a393022074ad7f4d47af7c3f8a7ddf0ba2903f986a88649b0018ce1538c379b304a6a23801".decode('hex')) SIGNATURE_1 = bytearray("304402205545419c4aded39c7f194b3f8c828f90e8d9352c756f7c131ed50e189c02f29a02201b160503d7310df49055b04a327e185fc22dfe68f433594ed7ce526d99a5026001".decode('hex')) SIGNATURE_2 = bytearray("30440220634fbbfaaea74d42280a8c9e56c97418af04539f93458e85285d15462aec7712022041ba27a5644642a2f5b3c02610235ec2c6115bf4137bb51181cbc0a3a54dc0db01".decode('hex')) TRANSACTION = bytearray("0100000001008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c00000000fc004730440220634fbbfaaea74d42280a8c9e56c97418af04539f93458e85285d15462aec7712022041ba27a5644642a2f5b3c02610235ec2c6115bf4137bb51181cbc0a3a54dc0db0147304402205545419c4aded39c7f194b3f8c828f90e8d9352c756f7c131ed50e189c02f29a02201b160503d7310df49055b04a327e185fc22dfe68f433594ed7ce526d99a50260014c6952210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253aeffffffff02809698000000000017a914c0c3b6ada732c797881d00de6c350eec96e3d22287f02925020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c38700000000".decode('hex')) SECONDFACTOR_1 = "RELAXED MODE Powercycle then confirm use of 0.46 BTC with PIN" # Armory txoutscript output = get_output_script([["0.1", bytearray("a914c0c3b6ada732c797881d00de6c350eec96e3d22287".decode('hex'))], ["0.3599", bytearray("a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387".decode('hex'))]]); if output<>OUTPUT: raise BTChipException("Invalid output script encoding"); # Optional setup dongle = getDongle(True) app = btchip(dongle) try: app.setup(btchip.OPERATION_MODE_RELAXED_WALLET, btchip.FEATURE_RFC6979, 111, 196, "1234", None, btchip.QWERTY_KEYMAP, SEED) except Exception: pass # Authenticate app.verifyPin("1234") # Get the trusted input associated to the UTXO transaction = bitcoinTransaction(UTX) print transaction trustedInput = app.getTrustedInput(transaction, UTXO_INDEX) # Start composing the transaction app.startUntrustedTransaction(True, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) dongle.close() # Wait for the second factor confirmation # Done on the same application for test purposes, this is typically done in another window # or another computer for bigger transactions response = raw_input("Powercycle the dongle to get the second factor and powercycle again : ") if not response.startswith(SECONDFACTOR_1): raise BTChipException("Invalid second factor") # Get a reference to the dongle again, as it was disconnected dongle = getDongle(True) app = btchip(dongle) # Replay the transaction, this time continue it since the second factor is ready app.startUntrustedTransaction(False, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) # Provide the second factor to finalize the signature signature1 = app.untrustedHashSign("0'/0/1", response[len(response) - 4:]) if signature1 <> SIGNATURE_1: raise BTChipException("Invalid signature1") # Same thing for the second signature app.verifyPin("1234") app.startUntrustedTransaction(True, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) dongle.close() response = raw_input("Powercycle the dongle to get the second factor and powercycle again : ") if not response.startswith(SECONDFACTOR_1): raise BTChipException("Invalid second factor") dongle = getDongle(True) app = btchip(dongle) app.startUntrustedTransaction(False, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) signature2 = app.untrustedHashSign("0'/0/2", response[len(response) - 4:]) if signature2 <> SIGNATURE_2: raise BTChipException("Invalid signature2") # Finalize the transaction - build the redeem script and put everything together inputScript = get_p2sh_input_script(REDEEMSCRIPT, [signature2, signature1]) transaction = format_transaction(OUTPUT, [ [ trustedInput['value'], inputScript] ]) print "Generated transaction : " + str(transaction).encode('hex') if transaction <> TRANSACTION: raise BTChipException("Invalid transaction") # The transaction is ready to be broadcast, enjoy btchip-python-0.1.31/tests/testMultisigArmoryNo2FA.py000066400000000000000000000205561373656177100226050ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * from btchip.btchipUtils import * import json """ Signs a TX generated by Armory. That TX: { 'inputs': [{ 'p2shscript': '52210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253ae', 'supporttxhash_be': '0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300', 'sequence': 4294967295, 'keys': [{ 'dersighex': '', 'pubkeyhex': '0269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c6283', 'wltlochex': '' }, { 'dersighex': '', 'pubkeyhex': '02f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb5', 'wltlochex': '' }, { 'dersighex': '', 'pubkeyhex': '037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b9102', 'wltlochex': '' }], 'contriblabel': u '', 'supporttxhash_le': '008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c', 'contribid': 'JLBercZk', 'version': 1, 'inputvalue': 46000000, 'outpoint': '008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c00000000', 'magicbytes': '0b110907', 'supporttx': '01000000013e9fe12917d854a0e093b982eaa46990289e2262f2db9fc1bd3f13718f3c806e010000006b483045022100af668e482e3ed363f51b36ddabad7cdf20d177104c92b8676a5b14f51107179602206c4ecd67544c74c6689ca453e2157d0c0b8a4608d85956429d2615275a51c66f01210374db359a004626daf2fcf10b8601f5f39438848a6733c768e88ce0ad398ae79dffffffff0280e7bd020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c3876c8fc846000000001976a914af58f09cf65b213bb9bd181a94e133b4ad4d6b2788ac00000000', 'numkeys': 3, 'supporttxhash': '0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300', 'supporttxoutindex': 0 }], 'fee': 10000, 'locktimeint': 0, 'outputs': [{ 'txoutvalue': 10000000, 'authdata': '', 'contriblabel': '', 'p2shscript': '', 'scripttypeint': 4, 'isp2sh': True, 'txoutscript': 'a914c0c3b6ada732c797881d00de6c350eec96e3d22287', 'authmethod': 'NONE', 'hasaddrstr': True, 'contribid': '', 'version': 1, 'ismultisig': False, 'magicbytes': '0b110907', 'addrstr': '2NApUBXv4NB8pm834pHUajiUL6rvFaaj6N8', 'scripttypestr': 'Standard (P2SH)', 'wltlocator': '' }, { 'txoutvalue': 35990000, 'authdata': '', 'contriblabel': '', 'p2shscript': '', 'scripttypeint': 4, 'isp2sh': True, 'txoutscript': 'a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387', 'authmethod': 'NONE', 'hasaddrstr': True, 'contribid': '', 'version': 1, 'ismultisig': False, 'magicbytes': '0b110907', 'addrstr': '2NDuYxRrmAs2fRcMj4ew2F41aFp2PN9yiV1', 'scripttypestr': 'Standard (P2SH)', 'wltlocator': '' }], 'sumoutputs': 45990000, 'suminputs': 46000000, 'version': 1, 'numoutputs': 2, 'magicbytes': '0b110907', 'locktimedate': '', 'locktimeblock': 0, 'id': '8jkccikU', 'numinputs': 1 } Input comes from vout[0] of 0c1676b8fc1adaca53221290e242b8eb80fd6b89aa83f2fa0106f87e13388300. TX I want to generate is 0.10 to 2NApUBXv4NB8pm834pHUajiUL6rvFaaj6N8 The multisig address 2NDuYxRrmAs2fRcMj4ew2F41aFp2PN9yiV1 contains 0.46 BTC, and is generated using the public keys 0'/0/0, 0'/0/1, and 0'/0/2 from the seed below. """ # Run on non configured dongle or dongle configured with test seed below SEED = bytearray("1762F9A3007DBC825D0DD9958B04880284C88A10C57CF569BB3DADF7B1027F2D".decode('hex')) UTX = bytearray("01000000013e9fe12917d854a0e093b982eaa46990289e2262f2db9fc1bd3f13718f3c806e010000006b483045022100af668e482e3ed363f51b36ddabad7cdf20d177104c92b8676a5b14f51107179602206c4ecd67544c74c6689ca453e2157d0c0b8a4608d85956429d2615275a51c66f01210374db359a004626daf2fcf10b8601f5f39438848a6733c768e88ce0ad398ae79dffffffff0280e7bd020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c3876c8fc846000000001976a914af58f09cf65b213bb9bd181a94e133b4ad4d6b2788ac00000000".decode('hex')) UTXO_INDEX = 0 OUTPUT = bytearray("02809698000000000017a914c0c3b6ada732c797881d00de6c350eec96e3d22287f02925020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387".decode('hex')) # Armory p2shscript REDEEMSCRIPT = bytearray("52210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253ae".decode('hex')) SIGNATURE_0 = bytearray("3044022056cb1b781fd04cfe6c04756ad56d02e5512f3fe7f411bc22d1594da5c815a393022074ad7f4d47af7c3f8a7ddf0ba2903f986a88649b0018ce1538c379b304a6a23801".decode('hex')) SIGNATURE_1 = bytearray("304402205545419c4aded39c7f194b3f8c828f90e8d9352c756f7c131ed50e189c02f29a02201b160503d7310df49055b04a327e185fc22dfe68f433594ed7ce526d99a5026001".decode('hex')) SIGNATURE_2 = bytearray("30440220634fbbfaaea74d42280a8c9e56c97418af04539f93458e85285d15462aec7712022041ba27a5644642a2f5b3c02610235ec2c6115bf4137bb51181cbc0a3a54dc0db01".decode('hex')) # Armory supporttx TRANSACTION = bytearray("0100000001008338137ef80601faf283aa896bfd80ebb842e290122253cada1afcb876160c00000000fc004730440220634fbbfaaea74d42280a8c9e56c97418af04539f93458e85285d15462aec7712022041ba27a5644642a2f5b3c02610235ec2c6115bf4137bb51181cbc0a3a54dc0db0147304402205545419c4aded39c7f194b3f8c828f90e8d9352c756f7c131ed50e189c02f29a02201b160503d7310df49055b04a327e185fc22dfe68f433594ed7ce526d99a50260014c6952210269694830114e4b1f6ef565ce4efb933681032d30333c80df713df6b60a4c62832102f43b905e9e35ccd22757faedf9eceb652dc9ba198a3904d43f4298def0213eb521037b9e3578dd3b5559d613bc2641931e6ce7d55a9d081b07347888d7d17a2b910253aeffffffff02809698000000000017a914c0c3b6ada732c797881d00de6c350eec96e3d22287f02925020000000017a914e2a227eb40dfce902f2c1d80ddafa798b16d22c38700000000".decode('hex')) # Armory txoutscript output = get_output_script([["0.1", bytearray("a914c0c3b6ada732c797881d00de6c350eec96e3d22287".decode('hex'))], ["0.3599", bytearray("a914e2a227eb40dfce902f2c1d80ddafa798b16d22c387".decode('hex'))]]); if output<>OUTPUT: raise BTChipException("Invalid output script encoding"); # Optional setup dongle = getDongle(True) app = btchip(dongle) try: app.setup(btchip.OPERATION_MODE_RELAXED_WALLET, btchip.FEATURE_RFC6979|btchip.FEATURE_NO_2FA_P2SH, 111, 196, "1234", None, btchip.QWERTY_KEYMAP, SEED) except Exception: pass # Authenticate app.verifyPin("1234") # Get the trusted input associated to the UTXO transaction = bitcoinTransaction(UTX) print transaction trustedInput = app.getTrustedInput(transaction, UTXO_INDEX) # Start composing the transaction app.startUntrustedTransaction(True, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) signature1 = app.untrustedHashSign("0'/0/1", "") if signature1 <> SIGNATURE_1: raise BTChipException("Invalid signature1") # Same thing for the second signature app.startUntrustedTransaction(True, 0, [trustedInput], REDEEMSCRIPT) app.finalizeInputFull(OUTPUT) signature2 = app.untrustedHashSign("0'/0/2", "") if signature2 <> SIGNATURE_2: raise BTChipException("Invalid signature2") # Finalize the transaction - build the redeem script and put everything together inputScript = get_p2sh_input_script(REDEEMSCRIPT, [signature2, signature1]) transaction = format_transaction(OUTPUT, [ [ trustedInput['value'], inputScript] ]) print "Generated transaction : " + str(transaction).encode('hex') if transaction <> TRANSACTION: raise BTChipException("Invalid transaction") # The transaction is ready to be broadcast, enjoy btchip-python-0.1.31/tests/testSimpleTransaction.py000066400000000000000000000106701373656177100224630ustar00rootroot00000000000000""" ******************************************************************************* * 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 btchip.btchip import * from btchip.btchipUtils import * # Run on non configured dongle or dongle configured with test seed below SEED = bytearray("1762F9A3007DBC825D0DD9958B04880284E88F10C57CF569BB3DADF7B1027F2D".decode('hex')) UTX = bytearray("01000000014ea60aeac5252c14291d428915bd7ccd1bfc4af009f4d4dc57ae597ed0420b71010000008a47304402201f36a12c240dbf9e566bc04321050b1984cd6eaf6caee8f02bb0bfec08e3354b022012ee2aeadcbbfd1e92959f57c15c1c6debb757b798451b104665aa3010569b49014104090b15bde569386734abf2a2b99f9ca6a50656627e77de663ca7325702769986cf26cc9dd7fdea0af432c8e2becc867c932e1b9dd742f2a108997c2252e2bdebffffffff0281b72e00000000001976a91472a5d75c8d2d0565b656a5232703b167d50d5a2b88aca0860100000000001976a9144533f5fb9b4817f713c48f0bfe96b9f50c476c9b88ac00000000".decode('hex')) UTXO_INDEX = 1 ADDRESS = "1BTChipvU14XH6JdRiK9CaenpJ2kJR9RnC" AMOUNT = "0.0009" FEES = "0.0001" SECONDFACTOR_1 = "Powercycle then confirm transfer of 0.0009 BTC to 1BTChipvU14XH6JdRiK9CaenpJ2kJR9RnC fees 0.0001 BTC change 0 BTC with PIN" SIGNATURE = bytearray("3045022100ea6df031b47629590daf5598b6f0680ad0132d8953b401577f01e8cc46393fe602202201b7a19d706a0213dcfeb7033719b92c6fd58a2d1d53411de71c4d8353154b01".decode('hex')) TRANSACTION = bytearray("0100000001c773da236484dae8f0fdba3d7e0ba1d05070d1a34fc44943e638441262a04f10010000006b483045022100ea6df031b47629590daf5598b6f0680ad0132d8953b401577f01e8cc46393fe602202201b7a19d706a0213dcfeb7033719b92c6fd58a2d1d53411de71c4d8353154b01210348bb1fade0adde1bf202726e6db5eacd2063fce7ecf8bbfd17377f09218d5814ffffffff01905f0100000000001976a91472a5d75c8d2d0565b656a5232703b167d50d5a2b88ac00000000".decode('hex')) # Optional setup dongle = getDongle(True) app = btchip(dongle) try: app.setup(btchip.OPERATION_MODE_WALLET, btchip.FEATURE_RFC6979, 0x00, 0x05, "1234", None, btchip.QWERTY_KEYMAP, SEED) except Exception: pass # Authenticate app.verifyPin("1234") # Get the public key and compress it publicKey = compress_public_key(app.getWalletPublicKey("0'/0/0")['publicKey']) # Get the trusted input associated to the UTXO transaction = bitcoinTransaction(UTX) outputScript = transaction.outputs[UTXO_INDEX].script trustedInput = app.getTrustedInput(transaction, UTXO_INDEX) # Start composing the transaction app.startUntrustedTransaction(True, 0, [trustedInput], outputScript) outputData = app.finalizeInput(ADDRESS, AMOUNT, FEES, "0'/1/0") dongle.close() # Wait for the second factor confirmation # Done on the same application for test purposes, this is typically done in another window # or another computer for bigger transactions response = raw_input("Powercycle the dongle to get the second factor and powercycle again : ") if not response.startswith(SECONDFACTOR_1): raise BTChipException("Invalid second factor") # Get a reference to the dongle again, as it was disconnected dongle = getDongle(True) app = btchip(dongle) # Replay the transaction, this time continue it since the second factor is ready app.startUntrustedTransaction(False, 0, [trustedInput], outputScript) app.finalizeInput(ADDRESS, "0.0009", "0.0001", "0'/1/0") # Provide the second factor to finalize the signature signature = app.untrustedHashSign("0'/0/0", response[len(response) - 4:]) if signature <> SIGNATURE: raise BTChipException("Invalid signature") # Finalize the transaction - build the redeem script and put everything together inputScript = get_regular_input_script(signature, publicKey) transaction = format_transaction(outputData['outputData'], [ [ trustedInput['value'], inputScript] ]) print "Generated transaction : " + str(transaction).encode('hex') if transaction <> TRANSACTION: raise BTChipException("Invalid transaction") # The transaction is ready to be broadcast, enjoy btchip-python-0.1.31/ui/000077500000000000000000000000001373656177100150215ustar00rootroot00000000000000btchip-python-0.1.31/ui/make.sh000077500000000000000000000013671373656177100163040ustar00rootroot00000000000000#!/bin/bash pyuic4 personalization-00-start.ui -o ../btchip/ui/personalization00start.py pyuic4 personalization-01-seed.ui -o ../btchip/ui/personalization01seed.py pyuic4 personalization-02-security.ui -o ../btchip/ui/personalization02security.py pyuic4 personalization-03-config.ui -o ../btchip/ui/personalization03config.py pyuic4 personalization-04-finalize.ui -o ../btchip/ui/personalization04finalize.py pyuic4 personalization-seedbackup-01.ui -o ../btchip/ui/personalizationseedbackup01.py pyuic4 personalization-seedbackup-02.ui -o ../btchip/ui/personalizationseedbackup02.py pyuic4 personalization-seedbackup-03.ui -o ../btchip/ui/personalizationseedbackup03.py pyuic4 personalization-seedbackup-04.ui -o ../btchip/ui/personalizationseedbackup04.py btchip-python-0.1.31/ui/personalization-00-start.ui000066400000000000000000000045531373656177100221600ustar00rootroot00000000000000 Dialog 0 0 400 231 BTChip setup 120 20 231 31 20 75 true true BTChip setup 20 60 351 61 Your BTChip dongle is not set up - you'll be able to create a new wallet, or restore an existing one, and choose your security profile. true 310 200 75 25 Next 20 120 351 81 Sensitive information including your dongle PIN will be exchanged during this setup phase - it is recommended to execute it on a secure computer, disconnected from any network, especially if you restore a wallet backup. true 20 200 75 25 Cancel btchip-python-0.1.31/ui/personalization-01-seed.ui000066400000000000000000000071361373656177100217440ustar00rootroot00000000000000 Dialog 0 0 400 300 BTChip setup - seed 50 20 311 31 20 75 true true BTChip setup - seed (1/3) 20 60 351 61 Please select an option : either create a new wallet or restore an existing one true 20 130 94 21 New Wallet true buttonGroup 20 180 171 21 Restore wallet backup buttonGroup false 50 210 331 21 QLineEdit::Normal Enter an hexadecimal seed or a BIP 39 mnemonic code 10 270 75 25 Cancel 320 270 75 25 Next 130 240 171 31 true Mnemonic API is not available true btchip-python-0.1.31/ui/personalization-02-security.ui000066400000000000000000000125061373656177100226710ustar00rootroot00000000000000 Dialog 0 0 400 503 BTChip setup - security 20 20 361 31 20 75 true true BTChip setup - security (2/3) 10 60 351 61 Please choose a security profile true 20 110 81 21 Hardened true buttonGroup 20 210 81 21 PIN only buttonGroup 50 140 351 61 You need to remove the dongle and insert it again to get a second factor validation of all operations. Recommended for expert users and to be fully protected against malwares. true 50 230 351 61 You only need to enter a PIN once when inserting the dongle. Transactions are not protected against malwares true 10 470 75 25 Cancel 310 470 75 25 Next 10 300 351 61 Please choose a PIN associated to the BTChip dongle. The PIN protects the dongle in case it is stolen, and can be up to 32 characters. The dongle is wiped if a wrong PIN is entered 3 times in a row. true 20 380 161 31 Enter the new PIN : true 210 380 161 21 QLineEdit::Password 210 420 161 21 QLineEdit::Password 20 420 171 31 Repeat the new PIN : true btchip-python-0.1.31/ui/personalization-03-config.ui000066400000000000000000000060031373656177100222630ustar00rootroot00000000000000 Dialog 0 0 400 243 BTChip setup 30 10 361 31 20 75 true true BTChip setup - config (3/3) 20 50 351 61 Please select your keyboard type to type the second factor confirmation true 50 110 94 21 QWERTY true keyboardGroup 50 140 94 21 QWERTZ keyboardGroup 50 170 94 21 AZERTY keyboardGroup 10 210 75 25 Cancel 320 210 75 25 Next btchip-python-0.1.31/ui/personalization-04-finalize.ui000066400000000000000000000052251373656177100226250ustar00rootroot00000000000000 Dialog 0 0 400 267 BTChip setup - security 20 20 361 31 20 75 true true BTChip setup - completed 320 230 75 25 Finish 10 70 351 61 BTChip setup is completed. Please enter your PIN to validate it then press Finish true 50 140 121 21 BTChip PIN : true 200 140 181 21 QLineEdit::Password 120 170 171 31 true Remaining attempts true btchip-python-0.1.31/ui/personalization-seedbackup-01.ui000066400000000000000000000062211373656177100231240ustar00rootroot00000000000000 Dialog 0 0 400 300 BTChip setup 30 20 351 31 20 75 true true BTChip setup - seed backup 320 270 75 25 Next 10 100 351 31 A new seed has been generated for your wallet. true 10 140 351 31 You must backup this seed and keep it out of reach of hackers (typically by keeping it on paper). true 10 180 351 41 You can use this seed to restore your dongle if you lose it or access your funds with any other compatible wallet. true 90 60 251 31 20 75 true true READ CAREFULLY 10 220 351 41 Press Next to start the backuping process. true btchip-python-0.1.31/ui/personalization-seedbackup-02.ui000066400000000000000000000027351373656177100231330ustar00rootroot00000000000000 Dialog 0 0 400 300 BTChip setup 30 20 351 31 20 75 true true BTChip setup - seed backup 20 70 351 31 Please disconnect the dongle then press Next true 320 270 75 25 Next btchip-python-0.1.31/ui/personalization-seedbackup-03.ui000066400000000000000000000104561373656177100231330ustar00rootroot00000000000000 Dialog 0 0 400 513 BTChip setup 20 10 351 31 20 75 true true BTChip setup - seed backup 20 50 351 61 If you do not trust this computer, perform the following steps on a trusted one or a different device. Anything supporting keyboard input will work (smartphone, TV box ...) true 20 120 351 31 Open a text editor, set the focus on the text editor, then insert the dongle true 20 160 351 51 After a very short time, the dongle will type the seed as hexadecimal (0..9 A..F) characters, starting with "seed" and ending with "X" true 20 220 351 51 If you perform those steps on Windows, a new device driver will be loaded the first time and the seed will not be typed. This is normal. true 20 280 351 71 If you perform those steps on Mac, you'll get a popup asking you to select a keyboard type the first time and the seed will not be typed. This is normal, just close the popup. true 20 350 351 51 If you did not see the seed for any reason, keep the focus on the text editor, unplug and plug the dongle again twice. true 20 410 351 51 Then press Next once you wrote the seed to a safe medium (i.e. paper) and unplugged the dongle true 310 480 75 25 Next btchip-python-0.1.31/ui/personalization-seedbackup-04.ui000066400000000000000000000036101373656177100231260ustar00rootroot00000000000000 Dialog 0 0 554 190 BTChip setup 30 10 351 31 20 75 true true BTChip setup - seed backup 10 50 351 51 Did you see the seed correctly displayed and did you backup it properly ? true 20 140 501 25 Yes, the seed is backed up properly and kept in a safe place, move on 20 110 501 25 No, I didn't see the seed. Wipe the dongle and start over