Beispiel #1
0
 def update(self, window: 'ElectrumWindow'):
     wallet = window.wallet
     if type(wallet) != Multisig_Wallet:
         return
     assert isinstance(wallet, Multisig_Wallet)  # only here for type-hints in IDE
     if self.listener is None:
         self.logger.info("starting listener")
         self.listener = Listener(self)
         self.listener.start()
     elif self.listener:
         self.logger.info("shutting down listener")
         self.listener.stop()
         self.listener = None
     self.keys = []
     self.cosigner_list = []
     for key, keystore in wallet.keystores.items():
         xpub = keystore.get_master_public_key()  # type: str
         pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes(compressed=True)
         _hash = bh2u(crypto.sha256d(pubkey))
         if not keystore.is_watching_only():
             self.keys.append((key, _hash, window))
         else:
             self.cosigner_list.append((window, xpub, pubkey, _hash))
     if self.listener:
         self.listener.set_keyhashes([t[1] for t in self.keys])
 def parse_message_signature(self, response, message, pubkey):
     
     # Prepend the message for signing as done inside the card!!
     message = to_bytes(message, 'utf8')
     hash = sha256d(msg_magic(message))
     coordx= pubkey.get_public_key_bytes()
     
     recid=-1
     for id in range(4):
         compsig=self.parse_to_compact_sig(response, id, compressed=True)
         # remove header byte
         compsig2= compsig[1:]
         
         try: 
             pk = ECPubkey.from_sig_string(compsig2, id, hash)
             pkbytes= pk.get_public_key_bytes(compressed=True)
         except InvalidECPointException:
             continue
         
         if coordx==pkbytes:
             recid=id
             break
     
     if recid == -1:
         raise ValueError("Unable to recover public key from signature")        
     
     return compsig
    def sign_message(self, sequence, message, password):
        sig = None
        try:
            message = message.encode('utf8')
            inputPath = self.get_derivation() + "/%d/%d" % sequence
            msg_hash = sha256d(msg_magic(message))
            inputHash = to_hexstr(msg_hash)
            hasharray = []
            hasharray.append({'hash': inputHash, 'keypath': inputPath})
            hasharray = json.dumps(hasharray)

            msg = ('{"sign":{"meta":"sign message", "data":%s}}' % hasharray).encode('utf8')

            dbb_client = self.plugin.get_client(self)

            if not dbb_client.is_paired():
                raise Exception(_("Could not sign message."))

            reply = dbb_client.hid_send_encrypt(msg)
            self.handler.show_message(_("Signing message ...") + "\n\n" +
                                      _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" +
                                      _("To cancel, briefly touch the blinking light or wait for the timeout."))
            reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)
            self.handler.finished()

            if 'error' in reply:
                raise Exception(reply['error']['message'])

            if 'sign' not in reply:
                raise Exception(_("Could not sign message."))

            if 'recid' in reply['sign'][0]:
                # firmware > v2.1.1
                sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
                recid = int(reply['sign'][0]['recid'], 16)
                sig = ecc.construct_sig65(sig_string, recid, True)
                pubkey, compressed = ecc.ECPubkey.from_signature65(sig, msg_hash)
                addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed))
                if ecc.verify_message_with_address(addr, sig, message) is False:
                    raise Exception(_("Could not sign message"))
            elif 'pubkey' in reply['sign'][0]:
                # firmware <= v2.1.1
                for recid in range(4):
                    sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
                    sig = ecc.construct_sig65(sig_string, recid, True)
                    try:
                        addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey']))
                        if ecc.verify_message_with_address(addr, sig, message):
                            break
                    except Exception:
                        continue
                else:
                    raise Exception(_("Could not sign message"))


        except BaseException as e:
            self.give_error(e)
        return sig
Beispiel #4
0
    def sign_message(self, sequence, message, password):
        sig = None
        try:
            message = message.encode('utf8')
            inputPath = self.get_derivation_prefix() + "/%d/%d" % sequence
            msg_hash = sha256d(msg_magic(message))
            inputHash = to_hexstr(msg_hash)
            hasharray = []
            hasharray.append({'hash': inputHash, 'keypath': inputPath})
            hasharray = json.dumps(hasharray)

            msg = ('{"sign":{"meta":"sign message", "data":%s}}' % hasharray).encode('utf8')

            dbb_client = self.plugin.get_client(self)

            if not dbb_client.is_paired():
                raise Exception(_("Could not sign message."))

            reply = dbb_client.hid_send_encrypt(msg)
            self.handler.show_message(_("Signing message ...") + "\n\n" +
                                      _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" +
                                      _("To cancel, briefly touch the blinking light or wait for the timeout."))
            reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented)
            self.handler.finished()

            if 'error' in reply:
                raise Exception(reply['error']['message'])

            if 'sign' not in reply:
                raise Exception(_("Could not sign message."))

            if 'recid' in reply['sign'][0]:
                # firmware > v2.1.1
                sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
                recid = int(reply['sign'][0]['recid'], 16)
                sig = ecc.construct_sig65(sig_string, recid, True)
                pubkey, compressed = ecc.ECPubkey.from_signature65(sig, msg_hash)
                addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed))
                if ecc.verify_message_with_address(addr, sig, message) is False:
                    raise Exception(_("Could not sign message"))
            elif 'pubkey' in reply['sign'][0]:
                # firmware <= v2.1.1
                for recid in range(4):
                    sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
                    sig = ecc.construct_sig65(sig_string, recid, True)
                    try:
                        addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey']))
                        if ecc.verify_message_with_address(addr, sig, message):
                            break
                    except Exception:
                        continue
                else:
                    raise Exception(_("Could not sign message"))


        except BaseException as e:
            self.give_error(e)
        return sig
Beispiel #5
0
 def update(self, window):
     wallet = window.wallet
     if type(wallet) != Multisig_Wallet:
         return
     if self.listener is None:
         self.print_error("starting listener")
         self.listener = Listener(self)
         self.listener.start()
     elif self.listener:
         self.print_error("shutting down listener")
         self.listener.stop()
         self.listener = None
     self.keys = []
     self.cosigner_list = []
     for key, keystore in wallet.keystores.items():
         xpub = keystore.get_master_public_key()
         K = bip32.deserialize_xpub(xpub)[-1]
         _hash = bh2u(crypto.sha256d(K))
         if not keystore.is_watching_only():
             self.keys.append((key, _hash, window))
         else:
             self.cosigner_list.append((window, xpub, K, _hash))
     if self.listener:
         self.listener.set_keyhashes([t[1] for t in self.keys])
Beispiel #6
0
 def update(self, window):
     wallet = window.wallet
     if type(wallet) != Multisig_Wallet:
         return
     if self.listener is None:
         self.print_error("starting listener")
         self.listener = Listener(self)
         self.listener.start()
     elif self.listener:
         self.print_error("shutting down listener")
         self.listener.stop()
         self.listener = None
     self.keys = []
     self.cosigner_list = []
     for key, keystore in wallet.keystores.items():
         xpub = keystore.get_master_public_key()
         K = bip32.deserialize_xpub(xpub)[-1]
         _hash = bh2u(crypto.sha256d(K))
         if not keystore.is_watching_only():
             self.keys.append((key, _hash, window))
         else:
             self.cosigner_list.append((window, xpub, K, _hash))
     if self.listener:
         self.listener.set_keyhashes([t[1] for t in self.keys])
Beispiel #7
0
    def sign_transaction(self, tx, password):
        if tx.is_complete():
            return

        try:
            p2pkhTransaction = True
            derivations = self.get_tx_derivations(tx)
            inputhasharray = []
            hasharray = []
            pubkeyarray = []

            # Build hasharray from inputs
            for i, txin in enumerate(tx.inputs()):
                if txin['type'] == 'coinbase':
                    self.give_error(
                        "Coinbase not supported")  # should never happen

                if txin['type'] != 'p2pkh':
                    p2pkhTransaction = False

                for x_pubkey in txin['x_pubkeys']:
                    if x_pubkey in derivations:
                        index = derivations.get(x_pubkey)
                        inputPath = "%s/%d/%d" % (self.get_derivation(),
                                                  index[0], index[1])
                        inputHash = sha256d(
                            binascii.unhexlify(tx.serialize_preimage(i)))
                        hasharray_i = {
                            'hash': to_hexstr(inputHash),
                            'keypath': inputPath
                        }
                        hasharray.append(hasharray_i)
                        inputhasharray.append(inputHash)
                        break
                else:
                    self.give_error("No matching x_key for sign_transaction"
                                    )  # should never happen

            # Build pubkeyarray from outputs
            for o in tx.outputs():
                assert o.type == TYPE_ADDRESS
                info = tx.output_info.get(o.address)
                if info is not None:
                    index = info.address_index
                    changePath = self.get_derivation() + "/%d/%d" % index
                    changePubkey = self.derive_pubkey(index[0], index[1])
                    pubkeyarray_i = {
                        'pubkey': changePubkey,
                        'keypath': changePath
                    }
                    pubkeyarray.append(pubkeyarray_i)

            # Special serialization of the unsigned transaction for
            # the mobile verification app.
            # At the moment, verification only works for p2pkh transactions.
            if p2pkhTransaction:

                class CustomTXSerialization(Transaction):
                    @classmethod
                    def input_script(self, txin, estimate_size=False):
                        if txin['type'] == 'p2pkh':
                            return Transaction.get_preimage_script(txin)
                        if txin['type'] == 'p2sh':
                            # Multisig verification has partial support, but is disabled. This is the
                            # expected serialization though, so we leave it here until we activate it.
                            return '00' + push_script(
                                Transaction.get_preimage_script(txin))
                        raise Exception("unsupported type %s" % txin['type'])

                tx_dbb_serialized = CustomTXSerialization(
                    tx.serialize()).serialize_to_network()
            else:
                # We only need this for the signing echo / verification.
                tx_dbb_serialized = None

            # Build sign command
            dbb_signatures = []
            steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)
            for step in range(int(steps)):
                hashes = hasharray[step * self.maxInputs:(step + 1) *
                                   self.maxInputs]

                msg = {
                    "sign": {
                        "data": hashes,
                        "checkpub": pubkeyarray,
                    },
                }
                if tx_dbb_serialized is not None:
                    msg["sign"]["meta"] = to_hexstr(sha256d(tx_dbb_serialized))
                msg = json.dumps(msg).encode('ascii')
                dbb_client = self.plugin.get_client(self)

                if not dbb_client.is_paired():
                    raise Exception("Could not sign transaction.")

                reply = dbb_client.hid_send_encrypt(msg)
                if 'error' in reply:
                    raise Exception(reply['error']['message'])

                if 'echo' not in reply:
                    raise Exception("Could not sign transaction.")

                if self.plugin.is_mobile_paired(
                ) and tx_dbb_serialized is not None:
                    reply['tx'] = tx_dbb_serialized
                    self.plugin.comserver_post_notification(reply)

                if steps > 1:
                    self.handler.show_message(
                        _("Signing large transaction. Please be patient ...") +
                        "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for 3 seconds."
                          ) + " " +
                        _("(Touch {} of {})").format((step + 1), steps) +
                        "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ) + "\n\n")
                else:
                    self.handler.show_message(
                        _("Signing transaction...") + "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for 3 seconds."
                          ) + "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ))

                # Send twice, first returns an echo for smart verification
                reply = dbb_client.hid_send_encrypt(msg)
                self.handler.finished()

                if 'error' in reply:
                    if reply["error"].get('code') in (600, 601):
                        # aborted via LED short touch or timeout
                        raise UserCancelled()
                    raise Exception(reply['error']['message'])

                if 'sign' not in reply:
                    raise Exception("Could not sign transaction.")

                dbb_signatures.extend(reply['sign'])

            # Fill signatures
            if len(dbb_signatures) != len(tx.inputs()):
                raise Exception("Incorrect number of transactions signed."
                                )  # Should never occur
            for i, txin in enumerate(tx.inputs()):
                num = txin['num_sig']
                for pubkey in txin['pubkeys']:
                    signatures = list(filter(None, txin['signatures']))
                    if len(signatures) == num:
                        break  # txin is complete
                    ii = txin['pubkeys'].index(pubkey)
                    signed = dbb_signatures[i]
                    if 'recid' in signed:
                        # firmware > v2.1.1
                        recid = int(signed['recid'], 16)
                        s = binascii.unhexlify(signed['sig'])
                        h = inputhasharray[i]
                        pk = ecc.ECPubkey.from_sig_string(s, recid, h)
                        pk = pk.get_public_key_hex(compressed=True)
                    elif 'pubkey' in signed:
                        # firmware <= v2.1.1
                        pk = signed['pubkey']
                    if pk != pubkey:
                        continue
                    sig_r = int(signed['sig'][:64], 16)
                    sig_s = int(signed['sig'][64:], 16)
                    sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
                    sig = to_hexstr(sig) + '01'
                    tx.add_signature_to_txin(i, ii, sig)
        except UserCancelled:
            raise
        except BaseException as e:
            self.give_error(e, True)
        else:
            _logger.info("Transaction is_complete {tx.is_complete()}")
            tx.raw = tx.serialize()
Beispiel #8
0
def derive_keys(x):
    h = sha256d(x)
    h = hashlib.sha512(h).digest()
    return (h[:32], h[32:])
Beispiel #9
0
 def test_sha256d(self):
     self.assertEqual(
         b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4',
         sha256d(u"test"))
    def sign_transaction(self, tx, password):
        if tx.is_complete():
            return

        try:
            p2pkhTransaction = True
            inputhasharray = []
            hasharray = []
            pubkeyarray = []

            # Build hasharray from inputs
            for i, txin in enumerate(tx.inputs()):
                if txin.is_coinbase_input():
                    self.give_error(
                        "Coinbase not supported")  # should never happen

                if txin.script_type != 'p2pkh':
                    p2pkhTransaction = False

                my_pubkey, inputPath = self.find_my_pubkey_in_txinout(txin)
                if not inputPath:
                    self.give_error("No matching pubkey for sign_transaction"
                                    )  # should never happen
                inputPath = convert_bip32_intpath_to_strpath(inputPath)
                inputHash = sha256d(bfh(tx.serialize_preimage(i)))
                hasharray_i = {
                    'hash': to_hexstr(inputHash),
                    'keypath': inputPath
                }
                hasharray.append(hasharray_i)
                inputhasharray.append(inputHash)

            # Build pubkeyarray from outputs
            for txout in tx.outputs():
                assert txout.address
                if txout.is_change:
                    changePubkey, changePath = self.find_my_pubkey_in_txinout(
                        txout)
                    assert changePath
                    changePath = convert_bip32_intpath_to_strpath(changePath)
                    changePubkey = changePubkey.hex()
                    pubkeyarray_i = {
                        'pubkey': changePubkey,
                        'keypath': changePath
                    }
                    pubkeyarray.append(pubkeyarray_i)

            # Special serialization of the unsigned transaction for
            # the mobile verification app.
            # At the moment, verification only works for p2pkh transactions.
            if p2pkhTransaction:
                tx_copy = copy.deepcopy(tx)

                # monkey-patch method of tx_copy instance to change serialization
                def input_script(self,
                                 txin: PartialTxInput,
                                 *,
                                 estimate_size=False):
                    if txin.script_type == 'p2pkh':
                        return Transaction.get_preimage_script(txin)
                    raise Exception("unsupported type %s" % txin.script_type)

                tx_copy.input_script = input_script.__get__(
                    tx_copy, PartialTransaction)
                tx_dbb_serialized = tx_copy.serialize_to_network()
            else:
                # We only need this for the signing echo / verification.
                tx_dbb_serialized = None

            # Build sign command
            dbb_signatures = []
            steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)
            for step in range(int(steps)):
                hashes = hasharray[step * self.maxInputs:(step + 1) *
                                   self.maxInputs]

                msg = {
                    "sign": {
                        "data": hashes,
                        "checkpub": pubkeyarray,
                    },
                }
                if tx_dbb_serialized is not None:
                    msg["sign"]["meta"] = to_hexstr(sha256d(tx_dbb_serialized))
                msg = json.dumps(msg).encode('ascii')
                dbb_client = self.plugin.get_client(self)

                if not dbb_client.is_paired():
                    raise Exception("Could not sign transaction.")

                reply = dbb_client.hid_send_encrypt(msg)
                if 'error' in reply:
                    raise Exception(reply['error']['message'])

                if 'echo' not in reply:
                    raise Exception("Could not sign transaction.")

                if self.plugin.is_mobile_paired(
                ) and tx_dbb_serialized is not None:
                    reply['tx'] = tx_dbb_serialized
                    self.plugin.comserver_post_notification(reply)

                if steps > 1:
                    self.handler.show_message(
                        _("Signing large transaction. Please be patient ...") +
                        "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for 3 seconds."
                          ) + " " +
                        _("(Touch {} of {})").format((step + 1), steps) +
                        "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ) + "\n\n")
                else:
                    self.handler.show_message(
                        _("Signing transaction...") + "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for 3 seconds."
                          ) + "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ))

                # Send twice, first returns an echo for smart verification
                reply = dbb_client.hid_send_encrypt(msg)
                self.handler.finished()

                if 'error' in reply:
                    if reply["error"].get('code') in (600, 601):
                        # aborted via LED short touch or timeout
                        raise UserCancelled()
                    raise Exception(reply['error']['message'])

                if 'sign' not in reply:
                    raise Exception("Could not sign transaction.")

                dbb_signatures.extend(reply['sign'])

            # Fill signatures
            if len(dbb_signatures) != len(tx.inputs()):
                raise Exception("Incorrect number of transactions signed."
                                )  # Should never occur
            for i, txin in enumerate(tx.inputs()):
                for pubkey_bytes in txin.pubkeys:
                    if txin.is_complete():
                        break
                    signed = dbb_signatures[i]
                    if 'recid' in signed:
                        # firmware > v2.1.1
                        recid = int(signed['recid'], 16)
                        s = binascii.unhexlify(signed['sig'])
                        h = inputhasharray[i]
                        pk = ecc.ECPubkey.from_sig_string(s, recid, h)
                        pk = pk.get_public_key_hex(compressed=True)
                    elif 'pubkey' in signed:
                        # firmware <= v2.1.1
                        pk = signed['pubkey']
                    if pk != pubkey_bytes.hex():
                        continue
                    sig_r = int(signed['sig'][:64], 16)
                    sig_s = int(signed['sig'][64:], 16)
                    sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
                    sig = to_hexstr(sig) + '01'
                    tx.add_signature_to_txin(txin_idx=i,
                                             signing_pubkey=pubkey_bytes.hex(),
                                             sig=sig)
        except UserCancelled:
            raise
        except BaseException as e:
            self.give_error(e, True)
        else:
            _logger.info(f"Transaction is_complete {tx.is_complete()}")
    def sign_transaction(self, tx, password):
        _logger.info(f"[Satochip_KeyStore] sign_transaction(): tx: {str(tx)}") #debugSatochip
        client = self.get_client()
        segwitTransaction = False
        
        # outputs
        txOutputs= ''.join(tx.serialize_output(o) for o in tx.outputs())
        hashOutputs = bh2u(sha256d(bfh(txOutputs)))
        txOutputs = var_int(len(tx.outputs()))+txOutputs
        _logger.info(f"[Satochip_KeyStore] sign_transaction(): hashOutputs= {hashOutputs}") #debugSatochip
        _logger.info(f"[Satochip_KeyStore] 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()):
            _logger.info(f"[Satochip_KeyStore] sign_transaction(): input= {str(i)} - 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

            if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
                segwitTransaction = True

            if txin['type'] in ['p2wpkh', 'p2wsh']:
                segwitTransaction = True
            
            pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
            for j, x_pubkey in enumerate(x_pubkeys):
                _logger.info(f"[Satochip_KeyStore] sign_transaction(): forforloop: j= {str(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 = sha256d(bfh(pre_tx_hex))
                    pre_hash_hex= pre_hash.hex()
                    _logger.info(f"[Satochip_KeyStore] sign_transaction(): pre_tx_hex= {pre_tx_hex}") #debugSatochip
                    _logger.info(f"[Satochip_KeyStore] sign_transaction(): pre_hash= {pre_hash_hex}") #debugSatochip
                    (response, sw1, sw2) = client.cc.card_parse_transaction(pre_tx, segwitTransaction)
                    (tx_hash, needs_2fa)= client.parser.parse_parse_transaction(response)
                    tx_hash_hex= bytearray(tx_hash).hex()
                    if pre_hash_hex!= tx_hash_hex:
                        raise RuntimeError("[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= 2 #see https://github.com/satoshilabs/slips/blob/master/slip-0044.md
                        test_net= constants.net.TESTNET
                        if segwitTransaction:
                            msg= {'tx':pre_tx_hex, 'ct':coin_type, 'tn':test_net, 'sw':segwitTransaction, 'txo':txOutputs, 'ty':txin['type']} 
                        else:
                            msg= {'tx':pre_tx_hex, 'ct':coin_type, 'tn':test_net, 'sw':segwitTransaction} 
                        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
                        # _logger.info(f"encrypted message: {msg_out}")
                        _logger.info(f"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.')
                        run_hook('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)
                        if reply_encrypt is None:
                            #todo: abort tx
                            break
                        reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
                        _logger.info(f"[Satochip_KeyStore] sign_transaction(): challenge:response= {reply_decrypt}")
                        reply_decrypt= reply_decrypt.split(":")
                        rep_pre_hash_hex= reply_decrypt[0]
                        if rep_pre_hash_hex!= pre_hash_hex:
                            #todo: abort tx or retry?
                            break
                        chalresponse=reply_decrypt[1]
                        if chalresponse=="00"*20:
                            #todo: abort tx?
                            break
                        chalresponse= list(bytes.fromhex(chalresponse))
                    else:
                        chalresponse= None
                    (tx_sig, sw1, sw2) = client.cc.card_sign_transaction(keynbr, tx_hash, chalresponse)
                    #_logger.info(f"sign_transaction(): sig= {bytearray(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()+'01'
                    tx.add_signature_to_txin(i,j,tx_sig)
                    break
            else:
                self.give_error("No matching x_key for sign_transaction") # should never happen
            
        _logger.info(f"[Satochip_KeyStore] sign_transaction(): Tx is complete: {str(tx.is_complete())}")
        tx.raw = tx.serialize()    
        return
Beispiel #12
0
 def test_sha256d(self):
     self.assertEqual(b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4',
                      sha256d(u"test"))
Beispiel #13
0
    def sign_transaction(self, tx, password):
        if tx.is_complete():
            return

        try:
            p2pkhTransaction = True
            derivations = self.get_tx_derivations(tx)
            inputhasharray = []
            hasharray = []
            pubkeyarray = []

            # Build hasharray from inputs
            for i, txin in enumerate(tx.inputs()):
                if txin['type'] == 'coinbase':
                    self.give_error("Coinbase not supported") # should never happen

                if txin['type'] != 'p2pkh':
                    p2pkhTransaction = False

                for x_pubkey in txin['x_pubkeys']:
                    if x_pubkey in derivations:
                        index = derivations.get(x_pubkey)
                        inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1])
                        inputHash = sha256d(binascii.unhexlify(tx.serialize_preimage(i)))
                        hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}
                        hasharray.append(hasharray_i)
                        inputhasharray.append(inputHash)
                        break
                else:
                    self.give_error("No matching x_key for sign_transaction") # should never happen

            # Build pubkeyarray from outputs
            for o in tx.outputs():
                assert o.type == TYPE_ADDRESS
                info = tx.output_info.get(o.address)
                if info is not None:
                    index = info.address_index
                    changePath = self.get_derivation() + "/%d/%d" % index
                    changePubkey = self.derive_pubkey(index[0], index[1])
                    pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath}
                    pubkeyarray.append(pubkeyarray_i)

            # Special serialization of the unsigned transaction for
            # the mobile verification app.
            # At the moment, verification only works for p2pkh transactions.
            if p2pkhTransaction:
                class CustomTXSerialization(Transaction):
                    @classmethod
                    def input_script(self, txin, estimate_size=False):
                        if txin['type'] == 'p2pkh':
                            return Transaction.get_preimage_script(txin)
                        if txin['type'] == 'p2sh':
                            # Multisig verification has partial support, but is disabled. This is the
                            # expected serialization though, so we leave it here until we activate it.
                            return '00' + push_script(Transaction.get_preimage_script(txin))
                        raise Exception("unsupported type %s" % txin['type'])
                tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize_to_network()
            else:
                # We only need this for the signing echo / verification.
                tx_dbb_serialized = None

            # Build sign command
            dbb_signatures = []
            steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)
            for step in range(int(steps)):
                hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs]

                msg = {
                    "sign": {
                        "data": hashes,
                        "checkpub": pubkeyarray,
                    },
                }
                if tx_dbb_serialized is not None:
                    msg["sign"]["meta"] = to_hexstr(sha256d(tx_dbb_serialized))
                msg = json.dumps(msg).encode('ascii')
                dbb_client = self.plugin.get_client(self)

                if not dbb_client.is_paired():
                    raise Exception("Could not sign transaction.")

                reply = dbb_client.hid_send_encrypt(msg)
                if 'error' in reply:
                    raise Exception(reply['error']['message'])

                if 'echo' not in reply:
                    raise Exception("Could not sign transaction.")

                if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None:
                    reply['tx'] = tx_dbb_serialized
                    self.plugin.comserver_post_notification(reply)

                if steps > 1:
                    self.handler.show_message(_("Signing large transaction. Please be patient ...") + "\n\n" +
                                              _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " +
                                              _("(Touch {} of {})").format((step + 1), steps) + "\n\n" +
                                              _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n")
                else:
                    self.handler.show_message(_("Signing transaction...") + "\n\n" +
                                              _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" +
                                              _("To cancel, briefly touch the blinking light or wait for the timeout."))

                # Send twice, first returns an echo for smart verification
                reply = dbb_client.hid_send_encrypt(msg)
                self.handler.finished()

                if 'error' in reply:
                    if reply["error"].get('code') in (600, 601):
                        # aborted via LED short touch or timeout
                        raise UserCancelled()
                    raise Exception(reply['error']['message'])

                if 'sign' not in reply:
                    raise Exception("Could not sign transaction.")

                dbb_signatures.extend(reply['sign'])

            # Fill signatures
            if len(dbb_signatures) != len(tx.inputs()):
                raise Exception("Incorrect number of transactions signed.") # Should never occur
            for i, txin in enumerate(tx.inputs()):
                num = txin['num_sig']
                for pubkey in txin['pubkeys']:
                    signatures = list(filter(None, txin['signatures']))
                    if len(signatures) == num:
                        break # txin is complete
                    ii = txin['pubkeys'].index(pubkey)
                    signed = dbb_signatures[i]
                    if 'recid' in signed:
                        # firmware > v2.1.1
                        recid = int(signed['recid'], 16)
                        s = binascii.unhexlify(signed['sig'])
                        h = inputhasharray[i]
                        pk = ecc.ECPubkey.from_sig_string(s, recid, h)
                        pk = pk.get_public_key_hex(compressed=True)
                    elif 'pubkey' in signed:
                        # firmware <= v2.1.1
                        pk = signed['pubkey']
                    if pk != pubkey:
                        continue
                    sig_r = int(signed['sig'][:64], 16)
                    sig_s = int(signed['sig'][64:], 16)
                    sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
                    sig = to_hexstr(sig) + '01'
                    tx.add_signature_to_txin(i, ii, sig)
        except UserCancelled:
            raise
        except BaseException as e:
            self.give_error(e, True)
        else:
            print_error("Transaction is_complete", tx.is_complete())
            tx.raw = tx.serialize()
Beispiel #14
0
def derive_keys(x):
    h = sha256d(x)
    h = hashlib.sha512(h).digest()
    return (h[:32],h[32:])