From 541ebe37f5dea9a3c52a02975d651ebeb76ebb34 Mon Sep 17 00:00:00 2001 From: slush Date: Tue, 13 Nov 2012 16:07:11 +0000 Subject: [PATCH] Client library for talking with bitkey device --- client.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 client.py diff --git a/client.py b/client.py new file mode 100644 index 000000000..ea9bded16 --- /dev/null +++ b/client.py @@ -0,0 +1,138 @@ +import os + +from bitkey_proto import bitkey_pb2 as proto +from transport_pipe import PipeTransport + +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 BitkeyClient(object): + + def __init__(self, path, message_func=show_message, input_func=show_input, debug=False): + self.master_public_key = None + self.path = path + self.connected = False + self.device = None + + self.message_func = message_func + self.input_func = input_func + self.debug = debug + + def open(self): + self.device = PipeTransport(self.path, False) + self.features = self.call(proto.Initialize()) + self.UUID = self.call(proto.GetUUID()) + + def close(self): + if self.device: + self.device.close() + self.device = None + + def _pprint(self, msg): + return "<%s>:\n%s" % (msg.__class__.__name__, msg) + + def call(self, msg, tries=3): + if self.debug: + print '----------------------' + print "Sending", self._pprint(msg) + + self.device.write(msg) + resp = self.device.read() + + if isinstance(resp, proto.OtpRequest): + otp = self.input_func("OTP required: ", resp.message) + self.device.write(proto.OtpAck(otp=otp)) + resp = self.device.read() + + if isinstance(resp, proto.PinRequest): + pin = self.input_func("PIN required: ", resp.message) + self.device.write(proto.PinAck(pin=pin)) + resp = self.device.read() + + if isinstance(resp, proto.Failure): + self.message_func(resp.message) + + if resp.code == 3: + if tries <= 1: + raise Exception("OTP is invalid, too many retries") + self.message_func("OTP is invalid, let's try again...") + + elif resp.code == 4: + raise Exception("Action cancelled by user") + + elif resp.code == 6: + if tries <= 1: + raise Exception("PIN is invalid, too many retries") + self.message_func("PIN is invalid, let's try again...") + + return self.call(msg, tries-1) + + if isinstance(resp, proto.Failure): + raise Exception(resp.code, resp.message) + + if self.debug: + print "Received", self._pprint(resp) + + return resp + + def sign_tx(self, algo, inputs, outputs, fee): + ''' + inputs: list of TxInput + outputs: list of TxOutput + ''' + + tx = proto.SignTx() + tx.algo = algo # Choose BIP32 or ELECTRUM way for deterministic keys + tx.random = os.urandom(256) # Provide additional entropy to the device + + for addr, amount in outputs: + if addr in self.addresses: + addr_n = self.addresses.index(addr) + else: + addr_n = None + + fee -= amount + output = tx.outputs.add() + output.address=addr + output.address_n.append(addr_n) + output.amount=amount + output.script_type=proto.PAYTOADDRESS + + print "FEE", fee + #print inputs2, outputs2 + + tx.fee = fee + 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 + '''