def sign_tx(self, msg): ''' This function starts workflow of signing Bitcoin transaction. Function set up the environment and send back a input request message, asking computer for first input. ''' self.set_main_state() if msg.inputs_count < 1: return proto.Failure( message='Transaction must have at least one input') if msg.outputs_count < 1: return proto.Failure( message='Transaction must have at least one output') if msg.random == '': return proto.Failure(message='No random data received') self.inputs_count = msg.inputs_count self.outputs_count = msg.outputs_count self.random = msg.random return proto.TxRequest(request_type=proto.TXINPUT, request_index=self.input_index)
def _check_address_n(self, msg): if len(msg.address_n): # Recalculate output address and compare with msg.address if msg.address != self.wallet.get_address(msg.address_n): self.set_main_state() return proto.Failure( message="address_n doesn't belong to given bitcoin address" )
def process_debug_message(self, msg): # Process messages handled by debugging connection try: return self._process_debug_message(msg) except Exception as exc: traceback.print_exc() self.set_main_state() return proto.Failure(message=str(exc))
def otp_check(self, otp): if otp == self.otp: msg = self.otp_func(*self.otp_args) self.otp_cancel() return msg else: time.sleep(3) self.otp_cancel() return proto.Failure(code=3, message="Invalid OTP")
def _process_debug_message(self, msg): if isinstance(msg, proto.DebugLinkGetState): # Report device state return self.debug_get_state(msg) if isinstance(msg, proto.LoadDevice): return self.debug_load_wallet(msg.algo, msg.seed, msg.otp, msg.pin, msg.spv) self.set_main_state() return proto.Failure(code=1, message="Unexpected message")
def tx_input(self, msg): ''' This message is called on tx input message. ''' if msg.index != self.input_index: self.set_main_state() return proto.Failure( message="Input index doesn't correspond with internal state") print "RECEIVED INPUT", msg if msg.index == self.signing_index: # Store message to cache for serializing input in tx_output self.signing_input = msg ''' There we have received one input. ''' if self.signing_index == 0: ''' If it is first one, we have to prepare and hash the beginning of the transaction. ''' if self.input_index == 0: # First input, let's hash the beginning of tx self.input_hash = hashlib.sha256( signing.raw_tx_header(self.inputs_count)) self.tx_hash = self.input_hash #self.tx_hash.update() #self.input_hash.update(signing.raw_tx_input()) #self.tx_hash.update(signing.raw_tx_input()) # TODO ''' For every input, hash the input itself. ''' print "INPUT HASH", self.input_hash.hexdigest() # TODO if self.input_index < self.inputs_count - 1: ''' If this is not the last input, request next input in the row. ''' self.input_index += 1 return proto.TxRequest(request_type=proto.TXINPUT, request_index=self.input_index) ''' We have processed all inputs. Let's request transaction outputs now. ''' self.output_index = 0 self.output_hash = hashlib.sha256() return proto.TxRequest(request_type=proto.TXOUTPUT, request_index=self.output_index)
def process_message(self, msg): # Any exception thrown during message processing # will result in Failure message instead of application crash try: ret = self._process_message(msg) if isinstance(ret, proto.Failure): self.set_main_state() return ret except Exception as exc: traceback.print_exc() self.set_main_state() return proto.Failure(message=str(exc))
def check(self, otp): # OTP is displayed with spaces, but they aren't significant otp = otp.replace(' ', '') if otp == self.otp: msg = self.func(*self.args) self.cancel() return msg else: time.sleep(3) self.cancel() self.set_main_state() return proto.Failure(code=3, message="Invalid OTP")
def pin_check(self, pin): if self.pin_pass_or_check: # Pass PIN to method msg = self.pin_func(pin, *self.pin_args) self.pin_cancel() return msg else: # Check PIN against device's internal PIN if pin == self.device.pin: msg = self.pin_func(*self.pin_args) self.pin_cancel() return msg else: time.sleep(3) self.pin_cancel() return proto.Failure(code=6, message="Invalid PIN")
def check(self, pin): if self.pass_or_check: # Pass PIN to method msg = self.func(pin, *self.args) self.cancel() return msg else: # Check PIN against device's internal PIN if pin == self.wallet.struct.pin: msg = self.func(*self.args) self.cancel() return msg else: time.sleep(3) self.cancel() self.set_main_state() return proto.Failure(code=6, message="Invalid PIN")
def protect_call(self, yesno_message, otp_message, pin_message, func, *args): if not self.yesno(yesno_message): return proto.Failure(code=4, message='Action cancelled by user') if self.device.otp: if self.device.pin: return self.otp_request( otp_message, self.pin_request, *[pin_message, False, func] + list(args)) else: return self.otp_request(otp_message, func, *args) if self.device.pin: return self.pin_request(pin_message, False, func, *args) return func(*args)
def process_message(self, msg): if isinstance(msg, proto.SignTx): # Start signing process return self.sign_tx(msg) if isinstance(msg, proto.TxInput): return self.tx_input(msg) if isinstance(msg, proto.TxOutput): if self.ser_output: # We just want to serialize part of output # and send it back to computer return self.serialize_output(msg) else: return self.tx_output(msg) # return Failure message to indicate problems to upstream SM return proto.Failure(code=1, message="Signing failed")
def resolve(self): if not self.func: # We're not waiting for hw buttons return if self.pending: # We still didn't received ButtonAck from computer return if self.decision == None: # We still don't know user's decision (call yesno_store() firstly) return if self.decision == True: ret = self.func(*self.args) else: self.set_main_state() ret = proto.Failure(code=4, message='Action cancelled by user') self.func = None self.args = [] return ret
def _check_output_index(self, msg): if msg.index != self.output_index: self.set_main_state() return proto.Failure( message="Output index doesn't correspond with internal state")
def _process_message(self, msg): if isinstance(msg, proto.Initialize): self.set_main_state() m = self.wallet.get_features() m.session_id = msg.session_id return m if self.otp.is_waiting(): '''OTP response is expected''' if isinstance(msg, proto.OtpAck): return self.otp.check(msg.otp) if isinstance(msg, proto.OtpCancel): self.otp.cancel() return proto.Success(message="OTP cancelled") self.set_main_state() return proto.Failure(code=2, message='OTP expected') if self.pin.is_waiting(): '''PIN response is expected''' if isinstance(msg, proto.PinAck): return self.pin.check(msg.pin) if isinstance(msg, proto.PinCancel): self.pin_cancel() return proto.Success(message="PIN request cancelled") self.set_main_state() return proto.Failure(code=5, message='PIN expected') if self.yesno.is_waiting(): '''Button confirmation is expected''' if isinstance(msg, proto.ButtonAck): self.yesno.allow() return self.yesno.resolve() # Process if button has been already pressed if isinstance(msg, proto.ButtonCancel): self.set_main_state() return proto.Success(message="Button confirmation cancelled") self.set_main_state() return proto.Failure(code=2, message='Button confirmation expected') if isinstance(msg, proto.Ping): return proto.Success(message=msg.message) if isinstance(msg, proto.GetUUID): return proto.UUID(UUID=self.wallet.get_UUID()) if isinstance(msg, proto.GetEntropy): return self.protect_call(["Send %d bytes" % msg.size, "of entropy", "to computer?"], '', '{ Cancel', 'Confirm }', None, None, self._get_entropy, msg.size) if isinstance(msg, proto.GetMasterPublicKey): return proto.MasterPublicKey(key=self.wallet.get_master_public_key()) if isinstance(msg, proto.GetAddress): address = self.wallet.get_address(list(msg.address_n)) self.layout.show_receiving_address(address) self.custom_message = True # Yes button will redraw screen return proto.Address(address=address) if isinstance(msg, proto.SetMaxFeeKb): return self.protect_call(["Current maximal fee", "is %s per kB." % self.wallet.maxfee_kb, "Set transaction fee", "to %s per kB?" % msg.maxfee_kb], '', '{ Cancel', 'Confirm }', None, None, self._set_maxfee_kb, msg.maxfee_kb) if isinstance(msg, proto.ResetDevice): return self.protect_reset("Reset device?", '', '{ Cancel', 'Confirm }', None, msg.random) if isinstance(msg, (proto.SignTx, proto.TxInput, proto.TxOutput)): return self.signing.process_message(msg) self.set_main_state() return proto.Failure(code=1, message="Unexpected message")
def process_message(self, msg): if isinstance(msg, proto.Initialize): self.otp_cancel() self.pin_cancel() m = proto.Features() m.session_id = msg.session_id m.vendor = self.device.vendor m.major_version = self.device.major_version m.minor_version = self.device.minor_version m.otp = self.device.otp == True m.pin = self.device.pin != '' m.spv = self.device.spv == True m.algo.extend(self.device.algo) m.maxfee_kb = self.device.maxfee_kb m.debug_link = self.device.debug_link return m if self.otp != None: '''OTP response is expected''' if isinstance(msg, proto.OtpAck): return self.otp_check(msg.otp) if isinstance(msg, proto.OtpCancel): self.otp_cancel() return proto.Success(message="OTP cancelled") return proto.Failure(code=2, message='Waiting for OTP') if self.pin_func != None: '''PIN response is expected''' if isinstance(msg, proto.PinAck): return self.pin_check(msg.pin) if isinstance(msg, proto.PinCancel): self.pin_cancel() return proto.Success(message="PIN request cancelled") return proto.Failure(code=5, message='Waiting for PIN') if isinstance(msg, proto.Ping): return proto.Success(message=msg.message) if isinstance(msg, proto.GetUUID): return proto.UUID(UUID='device-UUID') if isinstance(msg, proto.GetEntropy): return self.protect_call( "Send %d bytes of entropy to computer?" % msg.size, None, None, self._get_entropy, msg.size) if isinstance(msg, proto.GetMasterPublicKey): return proto.MasterPublicKey( key=self.device.get_master_public_key(msg.algo)) if isinstance(msg, proto.SetMaxFeeKb): return self.protect_call("Current maximal fee is %s per kB. Set transaction fee to %s per kilobyte?" % \ (self.device.maxfee_kb, msg.maxfee_kb), None, None, self._set_maxfee_kb, msg.maxfee_kb) if isinstance(msg, proto.LoadDevice): return self.protect_call("Load device with custom seed?", None, None, self._load_device, msg.seed, msg.otp, msg.pin, msg.spv) if isinstance(msg, proto.ResetDevice): return self.protect_call("Reset device?", None, None, self._reset_device, msg.random) if isinstance(msg, proto.SignTx): print "<TODO: Print transaction details>" return self.protect_call("Sign transaction?", None, None, self._sign_tx, msg) return proto.Failure(code=1, message='Unknown method')