diff --git a/bitkeylib/client.py b/bitkeylib/client.py deleted file mode 100644 index 24c5a554c5..0000000000 --- a/bitkeylib/client.py +++ /dev/null @@ -1,245 +0,0 @@ -import os -import time - -import trezor_pb2 as proto - -def show_message(message): - print "MESSAGE FROM DEVICE:", message - -def show_input(input_text, message=None): - if message: - print "QUESTION FROM DEVICE:", message - return raw_input(input_text) - -class CallException(Exception): - pass - -class PinException(CallException): - pass - -class BitkeyClient(object): - - def __init__(self, transport, debuglink=None, - message_func=show_message, input_func=show_input, debug=False): - self.transport = transport - self.debuglink = debuglink - - self.message_func = message_func - self.input_func = input_func - self.debug = debug - - self.setup_debuglink() - self.init_device() - - def _get_local_entropy(self): - return os.urandom(32) - - def init_device(self): - self.master_public_key = None - self.features = self.call(proto.Initialize()) - - def get_master_public_key(self): - if self.master_public_key: - return self.master_public_key - - self.master_public_key = self.call(proto.GetMasterPublicKey()).key - return self.master_public_key - - def get_address(self, n): - return self.call(proto.GetAddress(address_n=n)).address - - def get_entropy(self, size): - return self.call(proto.GetEntropy(size=size)).entropy - - def ping(self, msg): - return self.call(proto.Ping(message=msg)).message - - def get_serial_number(self): - return self.features.serial_number - - def apply_settings(self, label=None, coin_shortcut=None, language=None): - settings = proto.ApplySettings() - if label: - settings.label = label - if coin_shortcut: - settings.coin_shortcut = coin_shortcut - if language: - settings.language = language - return self.call(settings).message - - def _pprint(self, msg): - return "<%s>:\n%s" % (msg.__class__.__name__, msg) - - def setup_debuglink(self, button=None, pin_correct=False): - self.debug_button = button - self.debug_pin = pin_correct - - def call(self, msg): - if self.debug: - print '----------------------' - print "Sending", self._pprint(msg) - - try: - self.transport.session_begin() - - self.transport.write(msg) - resp = self.transport.read_blocking() - - if isinstance(resp, proto.ButtonRequest): - if self.debuglink and self.debug_button: - print "Pressing button", self.debug_button - self.debuglink.press_button(self.debug_button) - - return self.call(proto.ButtonAck()) - - if isinstance(resp, proto.PinMatrixRequest): - if self.debuglink: - if self.debug_pin: - pin = self.debuglink.read_pin_encoded() - msg2 = proto.PinMatrixAck(pin=pin) - else: - msg2 = proto.PinMatrixAck(pin='444444222222') - else: - pin = self.input_func("PIN required: ", resp.message) - msg2 = proto.PinMatrixAck(pin=pin) - - return self.call(msg2) - - finally: - self.transport.session_end() - - if isinstance(resp, proto.Failure): - self.message_func(resp.message) - - if resp.code == 4: - raise CallException("Action cancelled by user") - - elif resp.code == 6: - raise PinException("PIN is invalid") - - raise CallException(resp.code, resp.message) - - if self.debug: - print "Received", self._pprint(resp) - - return resp - - def sign_tx(self, inputs, outputs): - ''' - inputs: list of TxInput - outputs: list of TxOutput - - proto.TxInput(index=0, - address_n=0, - amount=0, - prev_hash='', - prev_index=0, - #script_sig= - ) - proto.TxOutput(index=0, - address='1Bitkey', - #address_n=[], - amount=100000000, - script_type=proto.PAYTOADDRESS, - #script_args= - ) - ''' - - start = time.time() - - try: - self.transport.session_begin() - - # Prepare and send initial message - tx = proto.SignTx() - tx.inputs_count = len(inputs) - tx.outputs_count = len(outputs) - res = self.call(tx) - - # Prepare structure for signatures - signatures = [None]*len(inputs) - serialized_tx = '' - - counter = 0 - while True: - counter += 1 - - if isinstance(res, proto.Failure): - raise CallException("Signing failed") - - if not isinstance(res, proto.TxRequest): - raise CallException("Unexpected message") - - # If there's some part of signed transaction, let's add it - if res.serialized_tx: - print "!!! RECEIVED PART OF SERIALIED TX (%d BYTES)" % len(res.serialized_tx) - serialized_tx += res.serialized_tx - - if res.signed_index >= 0 and res.signature: - print "!!! SIGNED INPUT", res.signed_index - signatures[res.signed_index] = res.signature - - if res.request_index < 0: - # Device didn't ask for more information, finish workflow - break - - # Device asked for one more information, let's process it. - if res.request_type == proto.TXOUTPUT: - res = self.call(outputs[res.request_index]) - continue - - elif res.request_type == proto.TXINPUT: - print "REQUESTING", res.request_index - res = self.call(inputs[res.request_index]) - continue - - finally: - self.transport.session_end() - - print "SIGNED IN %.03f SECONDS, CALLED %d MESSAGES, %d BYTES" % \ - (time.time() - start, counter, len(serialized_tx)) - - return (signatures, serialized_tx) - - #print "PBDATA", tx.SerializeToString().encode('hex') - - ################# - ################# - ################# - - ''' - signatures = [('add550d6ba9ab7e01d37e17658f98b6e901208d241f24b08197b5e20dfa7f29f095ae01acbfa5c4281704a64053dcb80e9b089ecbe09f5871d67725803e36edd', '3045022100dced96eeb43836bc95676879eac303eabf39802e513f4379a517475c259da12502201fd36c90ecd91a32b2ca8fed2e1755a7f2a89c2d520eb0da10147802bc7ca217')] - - s_inputs = [] - for i in range(len(inputs)): - addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] - pubkey = signatures[i][0].decode('hex') - sig = signatures[i][1].decode('hex') - s_inputs.append((addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig)) - - return s_inputs - - s_inputs = [] - for i in range(len(inputs)): - addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] - private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 ) - public_key = private_key.get_verifying_key() - pubkey = public_key.to_string() - tx = filter( raw_tx( inputs, outputs, for_sig = i ) ) - sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) - s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) ) - return s_inputs - ''' - - def reset_device(self): - # Begin with device reset workflow - raise Exception("Not implemented") - resp = self.call(proto.ResetDevice(random=self._get_local_entropy())) - self.init_device() - return isinstance(resp, proto.Success) - - def load_device(self, seed, pin): - resp = self.call(proto.LoadDevice(seed=seed, pin=pin)) - self.init_device() - return isinstance(resp, proto.Success) diff --git a/bitkeylib/transport_pipe.py b/bitkeylib/transport_pipe.py deleted file mode 100644 index ee27857d89..0000000000 --- a/bitkeylib/transport_pipe.py +++ /dev/null @@ -1,59 +0,0 @@ -'''TransportFake implements fake wire transport over local named pipe. -Use this transport for talking with bitkey simulator.''' - -import os -from select import select -from transport import Transport - -class PipeTransport(Transport): - def __init__(self, device, is_device, *args, **kwargs): - self.is_device = is_device # Set True if act as device - - super(PipeTransport, self).__init__(device, *args, **kwargs) - - def _open(self): - if self.is_device: - self.filename_read = self.device+'.to' - self.filename_write = self.device+'.from' - - os.mkfifo(self.filename_read, 0600) - os.mkfifo(self.filename_write, 0600) - else: - self.filename_read = self.device+'.from' - self.filename_write = self.device+'.to' - - if not os.path.exists(self.filename_write): - raise Exception("Not connected") - - self.write_fd = os.open(self.filename_write, os.O_RDWR)#|os.O_NONBLOCK) - self.write_f = os.fdopen(self.write_fd, 'w+') - - self.read_fd = os.open(self.filename_read, os.O_RDWR)#|os.O_NONBLOCK) - self.read_f = os.fdopen(self.read_fd, 'rb') - - def _close(self): - self.read_f.close() - self.write_f.close() - if self.is_device: - os.unlink(self.filename_read) - os.unlink(self.filename_write) - - def ready_to_read(self): - rlist, _, _ = select([self.read_f], [], [], 0) - return len(rlist) > 0 - - def _write(self, msg): - try: - self.write_f.write(msg) - self.write_f.flush() - except OSError: - print "Error while writing to socket" - raise - - def _read(self): - try: - (msg_type, datalen) = self._read_headers(self.read_f) - return (msg_type, self.read_f.read(datalen)) - except IOError: - print "Failed to read from device" - raise \ No newline at end of file diff --git a/bitkeylib/__init__.py b/trezorlib/__init__.py similarity index 100% rename from bitkeylib/__init__.py rename to trezorlib/__init__.py diff --git a/bitkeylib/debuglink.py b/trezorlib/debuglink.py similarity index 100% rename from bitkeylib/debuglink.py rename to trezorlib/debuglink.py diff --git a/bitkeylib/mapping.py b/trezorlib/mapping.py similarity index 100% rename from bitkeylib/mapping.py rename to trezorlib/mapping.py diff --git a/trezorlib/protobuf_json.py b/trezorlib/protobuf_json.py new file mode 100644 index 0000000000..146468efab --- /dev/null +++ b/trezorlib/protobuf_json.py @@ -0,0 +1,139 @@ +# JSON serialization support for Google's protobuf Messages +# Copyright (c) 2009, Paul Dovbush +# All rights reserved. +# http://code.google.com/p/protobuf-json/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +Provide serialization and de-serialization of Google's protobuf Messages into/from JSON format. +''' + +# groups are deprecated and not supported; +# Note that preservation of unknown fields is currently not available for Python (c) google docs +# extensions is not supported from 0.0.5 (due to gpb2.3 changes) + +__version__ = '0.0.5' +__author__ = 'Paul Dovbush ' + + +import json # py2.6+ TODO: add support for other JSON serialization modules +from google.protobuf.descriptor import FieldDescriptor as FD + + +class ParseError(Exception): pass + + +def json2pb(pb, js): + ''' convert JSON string to google.protobuf.descriptor instance ''' + for field in pb.DESCRIPTOR.fields: + if field.name not in js: + continue + if field.type == FD.TYPE_MESSAGE: + pass + elif field.type in _js2ftype: + ftype = _js2ftype[field.type] + else: + raise ParseError("Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type,)) + value = js[field.name] + if field.label == FD.LABEL_REPEATED: + pb_value = getattr(pb, field.name, None) + for v in value: + if field.type == FD.TYPE_MESSAGE: + json2pb(pb_value.add(), v) + else: + pb_value.append(ftype(v)) + else: + if field.type == FD.TYPE_MESSAGE: + json2pb(getattr(pb, field.name, None), value) + else: + setattr(pb, field.name, ftype(value)) + return pb + + + +def pb2json(pb): + ''' convert google.protobuf.descriptor instance to JSON string ''' + js = {} + # fields = pb.DESCRIPTOR.fields #all fields + fields = pb.ListFields() # only filled (including extensions) + for field, value in fields: + if field.type == FD.TYPE_MESSAGE: + ftype = pb2json + elif field.type in _ftype2js: + ftype = _ftype2js[field.type] + else: + raise ParseError("Field %s.%s of type '%d' is not supported" % (pb.__class__.__name__, field.name, field.type,)) + if field.label == FD.LABEL_REPEATED: + js_value = [] + for v in value: + js_value.append(ftype(v)) + else: + js_value = ftype(value) + js[field.name] = js_value + return js + + +_ftype2js = { + FD.TYPE_DOUBLE: float, + FD.TYPE_FLOAT: float, + FD.TYPE_INT64: long, + FD.TYPE_UINT64: long, + FD.TYPE_INT32: int, + FD.TYPE_FIXED64: float, + FD.TYPE_FIXED32: float, + FD.TYPE_BOOL: bool, + FD.TYPE_STRING: unicode, + # FD.TYPE_MESSAGE: pb2json, #handled specially + FD.TYPE_BYTES: lambda x: x.encode('string_escape'), + FD.TYPE_UINT32: int, + FD.TYPE_ENUM: int, + FD.TYPE_SFIXED32: float, + FD.TYPE_SFIXED64: float, + FD.TYPE_SINT32: int, + FD.TYPE_SINT64: long, +} + +_js2ftype = { + FD.TYPE_DOUBLE: float, + FD.TYPE_FLOAT: float, + FD.TYPE_INT64: long, + FD.TYPE_UINT64: long, + FD.TYPE_INT32: int, + FD.TYPE_FIXED64: float, + FD.TYPE_FIXED32: float, + FD.TYPE_BOOL: bool, + FD.TYPE_STRING: unicode, + # FD.TYPE_MESSAGE: json2pb, #handled specially + FD.TYPE_BYTES: lambda x: x.decode('string_escape'), + FD.TYPE_UINT32: int, + FD.TYPE_ENUM: int, + FD.TYPE_SFIXED32: float, + FD.TYPE_SFIXED64: float, + FD.TYPE_SINT32: int, + FD.TYPE_SINT64: long, +} diff --git a/bitkeylib/transport.py b/trezorlib/transport.py similarity index 100% rename from bitkeylib/transport.py rename to trezorlib/transport.py diff --git a/bitkeylib/transport_fake.py b/trezorlib/transport_fake.py similarity index 100% rename from bitkeylib/transport_fake.py rename to trezorlib/transport_fake.py diff --git a/bitkeylib/transport_hid.py b/trezorlib/transport_hid.py similarity index 100% rename from bitkeylib/transport_hid.py rename to trezorlib/transport_hid.py diff --git a/bitkeylib/transport_serial.py b/trezorlib/transport_serial.py similarity index 100% rename from bitkeylib/transport_serial.py rename to trezorlib/transport_serial.py diff --git a/bitkeylib/transport_socket.py b/trezorlib/transport_socket.py similarity index 100% rename from bitkeylib/transport_socket.py rename to trezorlib/transport_socket.py diff --git a/bitkeylib/trezor_pb2.py b/trezorlib/trezor_pb2.py similarity index 100% rename from bitkeylib/trezor_pb2.py rename to trezorlib/trezor_pb2.py