def sign_message(self, sequence, message, password): message_byte = message.encode('utf8') message_hash = hashlib.sha256(message_byte).hexdigest().upper() client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.print_error('debug: sign_message: path: '+address_path) self.handler.show_message("Signing message ...\r\nMessage hash: "+message_hash) # check if 2FA is required hmac=b'' if (client.cc.needs_2FA==None): (response, sw1, sw2, d)=client.cc.card_get_status() if client.cc.needs_2FA: # challenge based on sha256(btcheader+msg) # format & encrypt msg import json msg= {'action':"sign_msg", 'msg':message} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # print_error("encrypted message: "+msg_out) self.print_error("id_2FA: "+id_2FA) #do challenge-response with 2FA device... self.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: # Note the below give_error call will itself raise Message. :/ self.give_error("No response received from 2FA!", True) return reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) self.print_error("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= bytes.fromhex(chalresponse) try: keynbr= 0xFF #for extended key (depth, bytepath)= bip32path2bytes(address_path) (pubkey, chaincode)=client.cc.card_bip32_get_extendedkey(bytepath) (response2, sw1, sw2, compsig) = client.cc.card_sign_message(keynbr, pubkey, message_byte, hmac) if (compsig==b''): self.handler.show_error(_("Wrong signature!\nThe 2FA device may have rejected the action.")) except Exception as e: self.give_error(e, True) finally: self.handler.finished() return compsig
def reset_2FA(self, client): if client.cc.needs_2FA: # challenge based on ID_2FA # format & encrypt msg import json msg = {'action': "reset_2FA"} msg = json.dumps(msg) (id_2FA, msg_out) = client.cc.card_crypt_transaction_2FA(msg, True) d = {} d['msg_encrypt'] = msg_out d['id_2FA'] = id_2FA # print_error("encrypted message: "+msg_out) print_error("id_2FA: " + id_2FA) #do challenge-response with 2FA device... client.handler.show_message( '2FA request sent! Approve or reject request on your second device.' ) Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt = d['reply_encrypt'] except Exception as e: self.give_error("No response received from 2FA!", True) reply_decrypt = client.cc.card_crypt_transaction_2FA( reply_encrypt, False) print_error("challenge:response= " + reply_decrypt) reply_decrypt = reply_decrypt.split(":") chalresponse = reply_decrypt[1] hmac = list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = client.cc.card_reset_2FA_key(hmac) if (sw1 == 0x90 and sw2 == 0x00): msg = _("2FA reset successfully!") client.cc.needs_2FA = False client.handler.show_message(msg) elif (sw1 == 0x9c and sw2 == 0x17): msg = _( f"Failed to reset 2FA: \nyou must reset the seed first (error code {hex(256*sw1+sw2)})" ) client.handler.show_error(msg) elif (sw1 == 0x9c and sw2 == 0x0b): msg = _( f"Failed to reset 2FA: \nrequest rejected by 2FA device (error code: {hex(256*sw1+sw2)})" ) client.handler.show_message(msg) else: msg = _( f"Failed to reset 2FA with error code: {hex(256*sw1+sw2)}") client.handler.show_error(msg) else: msg = _(f"2FA is already disabled!") client.handler.show_error(msg)
def reset_seed(self, client): print_error("In reset_seed") # pin msg = ''.join([ _("WARNING!\n"), _("You are about to reset the seed of your Satochip. This process is irreversible!\n"), _("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n"), _("To proceed, enter the PIN for your Satochip:") ]) (password, reset_2FA)= self.reset_seed_dialog(msg) if (password is None): return pin = password.encode('utf8') pin= list(pin) # if 2FA is enabled, get challenge-response hmac=[] if (client.cc.needs_2FA==None): (response, sw1, sw2, d)=client.cc.card_get_status() if client.cc.needs_2FA: # challenge based on authentikey authentikeyx= bytearray(client.cc.parser.authentikey_coordx).hex() # format & encrypt msg import json msg= {'action':"reset_seed", 'authentikeyx':authentikeyx} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # print_error("encrypted message: "+msg_out) #print_error("id_2FA: "+id_2FA) #do challenge-response with 2FA device... client.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) print_error("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = client.cc.card_reset_seed(pin, hmac) if (sw1==0x90 and sw2==0x00): msg= _("Seed reset successfully!\nYou should close this wallet and launch the wizard to generate a new wallet.") client.handler.show_message(msg) #to do: close client? else: msg= _(f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}") client.handler.show_error(msg) if reset_2FA and client.cc.needs_2FA: # challenge based on ID_2FA # format & encrypt msg import json msg= {'action':"reset_2FA"} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # print_error("encrypted message: "+msg_out) print_error("id_2FA: "+id_2FA) #do challenge-response with 2FA device... client.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) print_error("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = client.cc.card_reset_2FA_key(hmac) if (sw1==0x90 and sw2==0x00): msg= _("2FA reset successfully!") client.cc.needs_2FA= False client.handler.show_message(msg) else: msg= _(f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}") client.handler.show_error(msg)
def system_tray(self, card_present): logger.debug('In system_tray') self.menu_def = [ 'BLANK', [ '&Setup new Satochip', '&Change PIN', '&Reset seed', '&Enable 2FA', '&About', '&Quit' ] ] if card_present: self.tray = sg.SystemTray(menu=self.menu_def, filename=self.satochip_icon) else: self.tray = sg.SystemTray(menu=self.menu_def, filename=self.satochip_unpaired_icon) while True: menu_item = self.tray.Read(timeout=1) if menu_item != '__TIMEOUT__': logger.debug('Menu item: ' + menu_item) ## Setup new Satochip ## if menu_item == 'Setup new Satochip': self.client.card_init_connect() ## Change PIN ## elif menu_item == 'Change PIN': msg_oldpin = ("Enter the current PIN for your Satochip:") msg_newpin = ("Enter a new PIN for your Satochip:") msg_confirm = ("Please confirm the new PIN for your Satochip:") msg_error = ( "The PIN values do not match! Please type PIN again!") msg_cancel = ("PIN change cancelled!") (is_PIN, oldpin, newpin) = self.client.PIN_change_dialog( msg_oldpin, msg_newpin, msg_confirm, msg_error, msg_cancel) if not is_PIN: continue else: oldpin = list(oldpin) newpin = list(newpin) (response, sw1, sw2) = self.client.cc.card_change_PIN(0, oldpin, newpin) if (sw1 == 0x90 and sw2 == 0x00): msg = ("PIN changed successfully!") self.show_success(msg) else: msg = ( f"Failed to change PIN with error code: {hex(sw1)}{hex(sw2)}" ) self.show_error(msg) ## Reset seed ## elif menu_item == 'Reset seed': msg = ''.join([ ("WARNING!\n"), ("You are about to reset the seed of your Satochip. This process is irreversible!\n" ), ("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n" ), ("To proceed, enter the PIN for your Satochip:") ]) (event, values) = self.reset_seed_dialog(msg) if event == 'Cancel': msg = ("Seed reset cancelled!") self.show_message(msg) continue pin = values['pin'] reset_2FA = values['reset_2FA'] pin = list(pin.encode('utf8')) # if 2FA is enabled, get challenge-response hmac = [] try: # todo: check if is_seeded self.client.cc.card_bip32_get_authentikey() self.client.cc.is_seeded = True except UninitializedSeedError: self.client.cc.is_seeded = False if self.client.cc.needs_2FA and self.client.cc.is_seeded: # challenge based on authentikey authentikeyx = bytearray( self.client.cc.parser.authentikey_coordx).hex() # format & encrypt msg import json msg = { 'action': "reset_seed", 'authentikeyx': authentikeyx } msg = json.dumps(msg) (id_2FA, msg_out) = self.client.cc.card_crypt_transaction_2FA( msg, True) d = {} d['msg_encrypt'] = msg_out d['id_2FA'] = id_2FA # logger.debug("encrypted message: "+msg_out) #do challenge-response with 2FA device... self.show_message( '2FA request sent! Approve or reject request on your second device.' ) Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt = d['reply_encrypt'] except Exception as e: self.show_error("No response received from 2FA...") continue reply_decrypt = self.client.cc.card_crypt_transaction_2FA( reply_encrypt, False) logger.debug("challenge:response= " + reply_decrypt) reply_decrypt = reply_decrypt.split(":") chalresponse = reply_decrypt[1] hmac = list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = self.client.cc.card_reset_seed(pin, hmac) if (sw1 == 0x90 and sw2 == 0x00): msg = ( "Seed reset successfully!\nYou can launch the wizard to setup your Satochip" ) self.show_success(msg) else: msg = ( f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}" ) self.show_error(msg) # reset 2FA if reset_2FA and self.client.cc.needs_2FA: # challenge based on ID_2FA # format & encrypt msg import json msg = {'action': "reset_2FA"} msg = json.dumps(msg) (id_2FA, msg_out) = self.client.cc.card_crypt_transaction_2FA( msg, True) d = {} d['msg_encrypt'] = msg_out d['id_2FA'] = id_2FA # _logger.info("encrypted message: "+msg_out) #do challenge-response with 2FA device... self.client.handler.show_message( '2FA request sent! Approve or reject request on your second device.' ) Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt = d['reply_encrypt'] except Exception as e: self.show_error("No response received from 2FA...") reply_decrypt = self.client.cc.card_crypt_transaction_2FA( reply_encrypt, False) logger.debug("challenge:response= " + reply_decrypt) reply_decrypt = reply_decrypt.split(":") chalresponse = reply_decrypt[1] hmac = list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = self.client.cc.card_reset_2FA_key(hmac) if (sw1 == 0x90 and sw2 == 0x00): self.client.cc.needs_2FA = False msg = ("2FA reset successfully!") self.show_success(msg) else: msg = ( f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}" ) self.show_error(msg) ## Enable 2FA ## elif menu_item == 'Enable 2FA': self.client.init_2FA() continue ## About ## elif menu_item == 'About': #copyright msg_copyright = ''.join([ '(c)2020 - Satochip by Toporin - https://github.com/Toporin/ \n', "This program is licensed under the GNU Lesser General Public License v3.0 \n", "This software is provided 'as-is', without any express or implied warranty.\n", "In no event will the authors be held liable for any damages arising from \n" "the use of this software." ]) #sw version # v_supported= (CardConnector.SATOCHIP_PROTOCOL_MAJOR_VERSION<<8)+CardConnector.SATOCHIP_PROTOCOL_MINOR_VERSION # sw_rel= str(CardConnector.SATOCHIP_PROTOCOL_MAJOR_VERSION) +'.'+ str(CardConnector.SATOCHIP_PROTOCOL_MINOR_VERSION) v_supported = (SATOCHIP_PROTOCOL_MAJOR_VERSION << 8) + SATOCHIP_PROTOCOL_MINOR_VERSION sw_rel = str(SATOCHIP_PROTOCOL_MAJOR_VERSION) + '.' + str( SATOCHIP_PROTOCOL_MINOR_VERSION) fw_rel = "N/A" is_seeded = "N/A" needs_2FA = "N/A" needs_SC = "N/A" msg_status = ( "Card is not initialized! \nClick on 'Setup new Satochip' in the menu to start configuration." ) (response, sw1, sw2, status) = self.client.cc.card_get_status() if (sw1 == 0x90 and sw2 == 0x00): #hw version v_applet = (status["protocol_major_version"] << 8) + status["protocol_minor_version"] fw_rel = str(status["protocol_major_version"]) + '.' + str( status["protocol_minor_version"]) # status if (v_supported < v_applet): msg_status = ( 'The version of your Satochip is higher than supported. \nYou should update Satochip-Bridge!' ) else: msg_status = 'Satochip-Bridge is up-to-date' # needs2FA? if len(response) >= 9 and response[8] == 0X01: needs_2FA = "yes" elif len(response) >= 9 and response[8] == 0X00: needs_2FA = "no" else: needs_2FA = "unknown" #is_seeded? if len(response) >= 10: is_seeded = "yes" if status["is_seeded"] else "no" else: #for earlier versions try: self.client.cc.card_bip32_get_authentikey() is_seeded = "yes" except UninitializedSeedError: is_seeded = "no" except Exception: is_seeded = "unknown" # secure channel if status["needs_secure_channel"]: needs_SC = "yes" else: needs_SC = "no" else: msg_status = 'No card found! please insert card!' frame_layout1 = [[ sg.Text('Supported Version: ', size=(20, 1)), sg.Text(sw_rel) ], [ sg.Text('Firmware Version: ', size=(20, 1)), sg.Text(fw_rel) ], [ sg.Text('Wallet is seeded: ', size=(20, 1)), sg.Text(is_seeded) ], [ sg.Text('Requires 2FA: ', size=(20, 1)), sg.Text(needs_2FA) ], [ sg.Text('Uses Secure Channel: ', size=(20, 1)), sg.Text(needs_SC) ]] frame_layout2 = [[ sg.Text(msg_status, justification='center', relief=sg.RELIEF_SUNKEN) ]] frame_layout3 = [[ sg.Text(msg_copyright, justification='center', relief=sg.RELIEF_SUNKEN) ]] layout = [[ sg.Frame('Satochip', frame_layout1, font='Any 12', title_color='blue') ], [ sg.Frame('Satochip status', frame_layout2, font='Any 12', title_color='blue') ], [ sg.Frame('About Satochip-Bridge', frame_layout3, font='Any 12', title_color='blue') ], [sg.Button('Ok')]] window = sg.Window('Satochip-Bridge: About', layout, icon=self.satochip_icon) event, value = window.read() window.close() del window continue ## Quit ## elif menu_item in (None, 'Quit'): break # check for handle requests from client through the queue self.reply() # exit after leaving the loop #sys.exit() # does not finish background thread os._exit( 0 ) # kill background thread but doesn't let the interpreter do any cleanup before the process dies
def sign_transaction(self, tx, password, *, use_cache=False): self.print_error('sign_transaction(): tx: '+ str(tx)) #debugSatochip client = self.get_client() # outputs txOutputs= ''.join(tx.serialize_output(o) for o in tx.outputs()) hashOutputs = bh2u(Hash(bfh(txOutputs))) txOutputs = var_int(len(tx.outputs()))+txOutputs self.print_error('sign_transaction(): hashOutputs= ', hashOutputs) #debugSatochip self.print_error('sign_transaction(): outputs= ', txOutputs) #debugSatochip # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for i,txin in enumerate(tx.inputs()): self.print_error('sign_transaction(): input =', i) #debugSatochip self.print_error('sign_transaction(): input[type]:', txin['type']) #debugSatochip if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for j, x_pubkey in enumerate(x_pubkeys): self.print_error('sign_transaction(): forforloop: j=', j) #debugSatochip if tx.is_txin_complete(txin): break if x_pubkey in derivations: signingPos = j s = derivations.get(x_pubkey) address_path = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) # get corresponing extended key (depth, bytepath)= bip32path2bytes(address_path) (key, chaincode)=client.cc.card_bip32_get_extendedkey(bytepath) # parse tx pre_tx_hex= tx.serialize_preimage(i) pre_tx= bytes.fromhex(pre_tx_hex)# hex representation => converted to bytes pre_hash = Hash(bfh(pre_tx_hex)) pre_hash_hex= pre_hash.hex() self.print_error('sign_transaction(): pre_tx_hex=', pre_tx_hex) #debugSatochip self.print_error('sign_transaction(): pre_hash=', pre_hash_hex) #debugSatochip #(response, sw1, sw2) = client.cc.card_parse_transaction(pre_tx, True) # use 'True' since BCH use BIP143 as in Segwit... #print_error('[satochip] sign_transaction(): response= '+str(response)) #debugSatochip #(tx_hash, needs_2fa) = client.parser.parse_parse_transaction(response) (response, sw1, sw2, tx_hash, needs_2fa) = client.cc.card_parse_transaction(pre_tx, True) # use 'True' since BCH use BIP143 as in Segwit... tx_hash_hex= bytearray(tx_hash).hex() if pre_hash_hex!= tx_hash_hex: raise RuntimeError(f"[Satochip_KeyStore] Tx preimage mismatch: {pre_hash_hex} vs {tx_hash_hex}") # sign tx keynbr= 0xFF #for extended key if needs_2fa: # format & encrypt msg import json coin_type= 145 #see https://github.com/satoshilabs/slips/blob/master/slip-0044.md test_net= networks.net.TESTNET msg= {'action':"sign_tx", 'tx':pre_tx_hex, 'ct':coin_type, 'sw':True, 'tn':test_net, 'txo':txOutputs, 'ty':txin['type']} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # self.print_error("encrypted message: "+msg_out) self.print_error("id_2FA:", id_2FA) #do challenge-response with 2FA device... client.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= None # init it in case of exc below reply_encrypt= d['reply_encrypt'] except Exception as e: # Note: give_error here will raise again.. :/ self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) break if reply_encrypt is None: #todo: abort tx break reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) self.print_error("challenge:response=", reply_decrypt) reply_decrypt= reply_decrypt.split(":") rep_pre_hash_hex= reply_decrypt[0][0:64] if rep_pre_hash_hex!= pre_hash_hex: #todo: abort tx or retry? self.print_error("Abort transaction: tx mismatch:",rep_pre_hash_hex,"!=",pre_hash_hex) break chalresponse=reply_decrypt[1] if chalresponse=="00"*20: #todo: abort tx? self.print_error("Abort transaction: rejected by 2FA!") break chalresponse= list(bytes.fromhex(chalresponse)) else: chalresponse= None (tx_sig, sw1, sw2) = client.cc.card_sign_transaction(keynbr, tx_hash, chalresponse) #self.print_error('sign_transaction(): sig=', bytes(tx_sig).hex()) #debugSatochip #todo: check sw1sw2 for error (0x9c0b if wrong challenge-response) # enforce low-S signature (BIP 62) tx_sig = bytearray(tx_sig) r,s= get_r_and_s_from_der_sig(tx_sig) if s > CURVE_ORDER//2: s = CURVE_ORDER - s tx_sig=der_sig_from_r_and_s(r, s) #update tx with signature tx_sig = tx_sig.hex()+'41' #tx.add_signature_to_txin(i,j,tx_sig) txin['signatures'][j] = tx_sig break else: self.give_error("No matching x_key for sign_transaction") # should never happen self.print_error("is_complete", tx.is_complete()) tx.raw = tx.serialize() return
def handleMessage(self): global cc, status, EXIT_SUCCESS, EXIT_FAILURE, logger logger.debug("In handleMessage()") logger.debug("Data: "+str(type(self.data))+" "+self.data) # parse msg try: msg= json.loads(self.data) action= msg["action"] except Exception as e: logger.warning("exception: "+repr(e)) try: if (action=="get_status"): response, sw1, sw2, status = cc.card_get_status() status["requestID"]= msg["requestID"] status["action"]= msg["action"] status['exitstatus']= EXIT_SUCCESS reply= json.dumps(status) self.sendMessage(reply) logger.debug("Reply: "+reply) elif (action=="get_chaincode"): path= msg["path"] #(depth, bytepath)= parser.bip32path2bytes(path) (pubkey, chaincode)= cc.card_bip32_get_extendedkey(path) # convert to string pubkey= pubkey.get_public_key_hex(False) # non-compressed hexstring chaincode= chaincode.hex() # hexstring d= {'requestID':msg["requestID"], 'action':msg["action"], 'pubkey':pubkey, 'chaincode':chaincode, 'exitstatus':EXIT_SUCCESS} reply= json.dumps(d) self.sendMessage(reply) logger.debug("Reply: "+reply) elif (action=="sign_tx_hash") or (action=="sign_msg_hash"): # prepare key corresponding to desired path path= msg["path"] #(depth, bytepath)= parser.bip32path2bytes(path) (pubkey, chaincode)= cc.card_bip32_get_extendedkey(path) logger.debug("Sign with pubkey: "+ pubkey.get_public_key_bytes(compressed=False).hex()) logger.debug("Sign hash: "+ msg["hash"]) keynbr=0xFF if cc.needs_2FA: #msg2FA= {'action':action, 'msg':message, 'alt':'etherlike'} msg_2FA= json.dumps(msg) (id_2FA, msg_2FA)= cc.card_crypt_transaction_2FA(msg_2FA, True) d={} d['msg_encrypt']= msg_2FA d['id_2FA']= id_2FA logger.debug("encrypted message: "+msg_2FA) logger.debug("id_2FA: "+ id_2FA) #do challenge-response with 2FA device... notif= '2FA request sent! Approve or reject request on your second device.' cc.client.request('show_notification', notif) #cc.client.request('show_message', notif) Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: cc.client.request('show_error', "No response received from 2FA...") reply_decrypt= cc.card_crypt_transaction_2FA(reply_encrypt, False) logger.debug("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= list(bytes.fromhex(chalresponse)) else: hmac=None logger.debug("Skip confirmation for this action? "+ str(wallets[self]) ) if not wallets[self]: #if confirm required request_action= "sign a message" if action=="sign_msg_hash" else "sign a transaction" request_msg= ("A client wants to perform the following on your Satochip:"+ "\n\tAction: "+ request_action + "\n\tAddress:"+ str(self.address)+ "\n\nApprove action?") (event, values)= cc.client.request('approve_action', request_msg) if event== 'No' or event== 'None': hmac=20*[0] # will trigger reject else: wallets[self]= values['skip_conf'] if (hmac==20*[0]): # rejected by 2FA or user d= {'requestID':msg["requestID"], 'action':msg["action"], "hash":msg["hash"], "sig":71*'00', "r":32*'00', "s":32*'00', "v":0 , "pubkey":pubkey.get_public_key_bytes().hex(), 'exitstatus':EXIT_FAILURE, 'reason':'Signing request rejected by user'} reply= json.dumps(d) self.sendMessage(reply) logger.debug("Reply: "+reply) else: hash= list(bytes.fromhex(msg["hash"])) (response, sw1, sw2)=cc.card_sign_transaction_hash(keynbr, hash, hmac) # convert sig to rsv format: logger.debug ("Convert sig to rsv format...") try: #compsig= parser.parse_hash_signature(response, bytes.fromhex(msg["hash"]), pubkey) (r,s,v, sigstring)= cc.parser.parse_rsv_from_dersig(bytes(response), bytes.fromhex(msg["hash"]), pubkey) # r,s,v:int convert to hex (64-char padded with 0) r= "{0:0{1}x}".format(r,64) s= "{0:0{1}x}".format(s,64) logger.debug("sigstring: " + sigstring.hex()) logger.debug ("r= " + r) logger.debug ("s= " + s) logger.debug ("v= " + str(v)) except Exception as e: logger.warning("Exception in parse_rsv_from_dersig: " + repr(e)) d= {'requestID':msg["requestID"], 'action':msg["action"], "hash":msg["hash"], "sig":sigstring.hex(), "r":r, "s":s, "v":v, "pubkey":pubkey.get_public_key_bytes().hex(), 'exitstatus':EXIT_SUCCESS} reply= json.dumps(d) self.sendMessage(reply) logger.debug("Reply: "+reply) else: d= {'requestID':msg['requestID'], 'action':msg['action'], 'exitstatus':EXIT_FAILURE, 'reason':'Action unknown'} reply= json.dumps(d) self.sendMessage(reply) logger.warning("Unknown action: "+action) except Exception as e: logger.warning('Exception: ' + repr(e)) cc.client.request('show_error','[handleMessage] Exception: '+repr(e))