예제 #1
0
 def show_qr(self):
     text = bfh(str(self.tx))
     text = base_encode(text, base=43)
     try:
         self.main_window.show_qrcode(text, _('Transaction'), parent=self)
     except Exception as e:
         self.show_message(str(e))
예제 #2
0
 def electrum_tx_to_txtype(self, tx, xpub_path):
     t = TransactionType()
     d = deserialize(tx.raw)
     t.version = d['version']
     t.lock_time = d['lockTime']
     t.inputs = self.tx_inputs(tx, xpub_path)
     t.bin_outputs = [
         TxOutputBinType(amount=vout['value'],
                         script_pubkey=bfh(vout['scriptPubKey']))
         for vout in d['outputs']
     ]
     return t
예제 #3
0
    def _make_multisig(self, m, xpubs, signatures=None):
        if len(xpubs) == 1:
            return None

        pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs]
        if signatures is None:
            signatures = [b''] * len(pubkeys)
        elif len(signatures) != len(pubkeys):
            raise RuntimeError('Mismatched number of signatures')
        else:
            signatures = [bfh(x)[:-1] if x else b'' for x in signatures]

        return MultisigRedeemScriptType(pubkeys=pubkeys,
                                        signatures=signatures,
                                        m=m)
예제 #4
0
 def electrum_tx_to_txtype(self, tx):
     t = self.types.TransactionType()
     if tx is None:
         # probably for segwit input and we don't need this prev txn
         return t
     d = deserialize(tx.raw)
     t.version = d['version']
     t.lock_time = d['lockTime']
     inputs = self.tx_inputs(tx)
     t.inputs.extend(inputs)
     for vout in d['outputs']:
         o = t.bin_outputs.add()
         o.amount = vout['value']
         o.script_pubkey = bfh(vout['scriptPubKey'])
     return t
예제 #5
0
 def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
     prev_tx = {
         bfh(txhash): self.electrum_tx_to_txtype(tx, xpub_path)
         for txhash, tx in prev_tx.items()
     }
     client = self.get_client(keystore)
     inputs = self.tx_inputs(tx, xpub_path, True)
     outputs = self.tx_outputs(keystore.get_derivation(), tx, client)
     details = SignTx(lock_time=tx.locktime)
     signatures, signed_tx = client.sign_tx(self.get_coin_name(),
                                            inputs,
                                            outputs,
                                            details=details,
                                            prev_txes=prev_tx)
     signatures = [bh2u(x) for x in signatures]
     tx.update_signatures(signatures)
예제 #6
0
    def tx_inputs(self, tx, xpub_path, for_sig=False):
        inputs = []
        for txin in tx.inputs():
            txinputtype = TxInputType()
            if txin['type'] == 'coinbase':
                prev_hash = b"\0" * 32
                prev_index = 0xffffffff  # signed int -1
            else:
                if for_sig:
                    x_pubkeys = txin['x_pubkeys']
                    xpubs = [parse_xpubkey(x) for x in x_pubkeys]
                    multisig = self._make_multisig(txin.get('num_sig'), xpubs,
                                                   txin.get('signatures'))
                    script_type = self.get_trezor_input_script_type(
                        multisig is not None)
                    txinputtype = TxInputType(script_type=script_type,
                                              multisig=multisig)
                    # find which key is mine
                    for xpub, deriv in xpubs:
                        if xpub in xpub_path:
                            xpub_n = parse_path(xpub_path[xpub])
                            txinputtype.address_n = xpub_n + deriv
                            break

                prev_hash = unhexlify(txin['prevout_hash'])
                prev_index = txin['prevout_n']

            if 'value' in txin:
                txinputtype.amount = txin['value']
            txinputtype.prev_hash = prev_hash
            txinputtype.prev_index = prev_index

            if 'scriptSig' in txin:
                script_sig = bfh(txin['scriptSig'])
                txinputtype.script_sig = script_sig

            txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)

            inputs.append(txinputtype)

        return inputs
예제 #7
0
 def do_send(self, d):
     tx = d.tx
     window, state = self._find_window_and_state_for_wallet(d.wallet)
     if not tx or not window or not state:
         self.print_error("Missing tx or window or state")
         return
     for xpub, K, _hash in state.cosigner_list:
         if not self.cosigner_can_sign(tx, xpub):
             continue
         message = bitcoin.encrypt_message(bfh(tx.raw),
                                           bh2u(K)).decode('ascii')
         try:
             state.server.put(_hash, message)
         except Exception as e:
             traceback.print_exc(file=sys.stdout)
             window.show_error(
                 _("Failed to send transaction to cosigning pool."))
             return
         d.show_message(
             _("Your transaction was sent to the cosigning pool.") + '\n' +
             _("Open your cosigner wallet to retrieve it."))
예제 #8
0
    def on_receive(self, window, keyhash, message):
        self.print_error("signal arrived for", keyhash, "@",
                         window.diagnostic_name())
        state = getattr(window, 'cosigner_pool_state', None)
        if not state:
            self.print_error("Error: state object not found")
            return
        keys = state.keys
        for key, _hash in keys:
            if _hash == keyhash:
                break
        else:
            self.print_error("keyhash not found")
            return

        wallet = window.wallet
        if isinstance(wallet.keystore, keystore.Hardware_KeyStore):
            window.show_warning(
                _('An encrypted transaction was retrieved from cosigning pool.'
                  ) + '\n' +
                _('However, hardware wallets do not support message decryption, '
                  'which makes them not compatible with the current design of cosigner pool.'
                  ))
            return
        password = None
        if wallet.has_password():
            password = window.password_dialog(
                _('An encrypted transaction was retrieved from cosigning pool.'
                  ) + '\n' + _('Please enter your password to decrypt it.'))
            if not password:
                return
        else:
            details = (_(
                "If you choose 'Yes', it will be decrypted and a transaction window will be shown, giving you the opportunity to sign the transaction."
            ) + "\n\n" + _(
                "If you choose 'No', you will be asked again later (the next time this wallet window is opened)."
            ))
            ret = window.msg_box(
                icon=QMessageBox.Question,
                parent=None,
                title=_("Cosigner Pool"),
                buttons=QMessageBox.Yes | QMessageBox.No,
                text=
                _("An encrypted transaction was retrieved from cosigning pool."
                  ) + '\n' + _("Do you want to open it now?"),
                detail_text=details)
            if ret != QMessageBox.Yes:
                return

        err, badpass = "******", False
        try:
            xprv = wallet.keystore.get_master_private_key(password)
        except InvalidPassword as e:
            err, badpass = str(e), True
            xprv = None
        if not xprv:
            window.show_error(err)
            if badpass:
                self.on_receive(window, keyhash, message)  # try again
            return
        try:
            k = bh2u(bitcoin.deserialize_xprv(xprv)[-1])
            EC = bitcoin.EC_KEY(bfh(k))
            message = bh2u(EC.decrypt_message(message))
        except Exception as e:
            traceback.print_exc(file=sys.stdout)
            window.show_error(repr(e))
            return

        state.listener.clear(keyhash)
        tx = transaction.Transaction(message)
        show_transaction(tx, window, prompt_if_unsaved=True)
예제 #9
0
    def sign_transaction(self, tx, password, *, use_cache=False):
        if tx.is_complete():
            return
        client = self.get_client()
        inputs = []
        inputsPaths = []
        pubKeys = []
        chipInputs = []
        redeemScripts = []
        signatures = []
        preparedTrustedInputs = []
        changePath = ""
        output = None
        p2shTransaction = False
        pin = ""
        self.get_client(
        )  # prompt for the PIN before displaying the dialog if necessary
        self.cashaddr_alert()

        # Fetch inputs of the transaction to sign
        derivations = self.get_tx_derivations(tx)
        for txin in tx.inputs():
            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 i, x_pubkey in enumerate(x_pubkeys):
                if x_pubkey in derivations:
                    signingPos = i
                    s = derivations.get(x_pubkey)
                    hwAddress = "{:s}/{:d}/{:d}".format(
                        self.get_derivation()[2:], s[0], s[1])
                    break
            else:
                self.give_error(_('No matching x_key for sign_transaction')
                                )  # should never happen

            redeemScript = Transaction.get_preimage_script(txin)
            inputs.append([
                txin['prev_tx'].raw, txin['prevout_n'], redeemScript,
                txin['prevout_hash'], signingPos,
                txin.get('sequence', 0xffffffff - 1)
            ])
            inputsPaths.append(hwAddress)
            pubKeys.append(pubkeys)

        # Sanity check
        if p2shTransaction:
            for txin in tx.inputs():
                if txin['type'] != 'p2sh':
                    self.give_error(
                        _('P2SH / regular input mixed in same transaction not supported'
                          ))  # should never happen

        txOutput = var_int(len(tx.outputs()))
        for txout in tx.outputs():
            output_type, addr, amount = txout
            txOutput += int_to_hex(amount, 8)
            script = tx.pay_script(addr)
            txOutput += var_int(len(script) // 2)
            txOutput += script
        txOutput = bfh(txOutput)

        # Recognize outputs
        # - only one output and one change is authorized (for hw.1 and nano)
        # - at most one output can bypass confirmation (~change) (for all)
        if not p2shTransaction:
            if not self.get_client_electrum().supports_multi_output():
                if len(tx.outputs()) > 2:
                    self.give_error(
                        _('Transaction with more than 2 outputs not supported by {}'
                          ).format(self.device))
            has_change = False
            any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
            for o in tx.outputs():
                _type, address, amount = o
                if self.get_client_electrum().is_hw1():
                    if not _type == TYPE_ADDRESS:
                        self.give_error(
                            _('Only address outputs are supported by {}').
                            format(self.device))
                else:
                    if not _type in [TYPE_ADDRESS, TYPE_SCRIPT]:
                        self.give_error(
                            _('Only address and script outputs are supported by {}'
                              ).format(self.device))
                    if _type == TYPE_SCRIPT:
                        try:
                            # Ledger has a maximum output size of 200 bytes:
                            # https://github.com/LedgerHQ/ledger-app-btc/commit/3a78dee9c0484821df58975803e40d58fbfc2c38#diff-c61ccd96a6d8b54d48f54a3bc4dfa7e2R26
                            # which gives us a maximum OP_RETURN payload size of
                            # 187 bytes. It also apparently has no limit on
                            # max_pushes, so we specify max_pushes=None so as
                            # to bypass that check.
                            validate_op_return_output_and_get_data(
                                o, max_size=187, max_pushes=None)
                        except RuntimeError as e:
                            self.give_error('{}: {}'.format(
                                self.device, str(e)))
                info = tx.output_info.get(address)
                if (info is not None) and len(tx.outputs()) > 1 \
                        and not has_change:
                    index, xpubs, m, script_type = info
                    on_change_branch = index[0] == 1
                    # prioritise hiding outputs on the 'change' branch from user
                    # because no more than one change address allowed
                    if on_change_branch == any_output_on_change_branch:
                        changePath = self.get_derivation(
                        )[2:] + "/{:d}/{:d}".format(*index)
                        has_change = True
                    else:
                        output = address
                else:
                    output = address

        self.handler.show_message(
            _('Confirm Transaction on your {}...').format(self.device))
        try:
            # Get trusted inputs from the original transactions
            for utxo in inputs:
                sequence = int_to_hex(utxo[5], 4)
                if not self.get_client_electrum().requires_trusted_inputs():
                    txtmp = bitcoinTransaction(bfh(utxo[0]))
                    tmp = bfh(utxo[3])[::-1]
                    tmp += bfh(int_to_hex(utxo[1], 4))
                    tmp += txtmp.outputs[utxo[1]].amount
                    chipInputs.append({
                        'value': tmp,
                        'witness': True,
                        'sequence': sequence
                    })
                    redeemScripts.append(bfh(utxo[2]))
                else:
                    txtmp = bitcoinTransaction(bfh(utxo[0]))
                    trustedInput = self.get_client().getTrustedInput(
                        txtmp, utxo[1])
                    trustedInput['sequence'] = sequence
                    trustedInput['witness'] = True
                    chipInputs.append(trustedInput)
                    if p2shTransaction:
                        redeemScripts.append(bfh(utxo[2]))
                    else:
                        redeemScripts.append(txtmp.outputs[utxo[1]].script)

            # Sign all inputs
            inputIndex = 0
            self.get_client().enableAlternate2fa(False)
            cashaddr = Address.FMT_UI == Address.FMT_CASHADDR
            if cashaddr and self.get_client_electrum().supports_cashaddr():
                self.get_client().startUntrustedTransaction(
                    True,
                    inputIndex,
                    chipInputs,
                    redeemScripts[inputIndex],
                    cashAddr=True)
            else:
                self.get_client().startUntrustedTransaction(
                    True, inputIndex, chipInputs, redeemScripts[inputIndex])
            # we don't set meaningful outputAddress, amount and fees
            # as we only care about the alternateEncoding==True branch
            outputData = self.get_client().finalizeInput(
                b'', 0, 0, changePath, bfh(tx.serialize(True)))
            outputData['outputData'] = txOutput
            transactionOutput = outputData['outputData']
            if outputData['confirmationNeeded']:
                outputData['address'] = output
                self.handler.finished()
                pin = self.handler.get_auth(
                    outputData)  # does the authenticate dialog and returns pin
                if not pin:
                    raise UserWarning()
                self.handler.show_message(
                    _('Confirmed. Signing Transaction...'))
            while inputIndex < len(inputs):
                singleInput = [chipInputs[inputIndex]]
                if cashaddr and self.get_client_electrum().supports_cashaddr():
                    self.get_client().startUntrustedTransaction(
                        False,
                        0,
                        singleInput,
                        redeemScripts[inputIndex],
                        cashAddr=True)
                else:
                    self.get_client().startUntrustedTransaction(
                        False, 0, singleInput, redeemScripts[inputIndex])
                inputSignature = self.get_client().untrustedHashSign(
                    inputsPaths[inputIndex],
                    pin,
                    lockTime=tx.locktime,
                    sighashType=0x41)
                inputSignature[0] = 0x30  # force for 1.4.9+
                signatures.append(inputSignature)
                inputIndex = inputIndex + 1
        except UserWarning:
            self.handler.show_error(_('Cancelled by user'))
            return
        except BTChipException as e:
            if e.sw in (0x6985, 0x6d00):  # cancelled by user
                return
            elif e.sw == 0x6982:
                raise  # pin lock. decorator will catch it
            else:
                traceback.print_exc(file=sys.stderr)
                self.give_error(e, True)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for i, txin in enumerate(tx.inputs()):
            signingPos = inputs[i][4]
            txin['signatures'][signingPos] = bh2u(signatures[i])
        tx.raw = tx.serialize()
예제 #10
0
    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 ERG 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 ERG 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
예제 #11
0
    def tx_inputs(self, tx, for_sig=False):
        inputs = []
        for txin in tx.inputs():
            txinputtype = self.types.TxInputType()
            if txin['type'] == 'coinbase':
                prev_hash = b"\x00" * 32
                prev_index = 0xffffffff  # signed int -1
            else:
                if for_sig:
                    x_pubkeys = txin['x_pubkeys']
                    if len(x_pubkeys) == 1:
                        x_pubkey = x_pubkeys[0]
                        xpub, s = parse_xpubkey(x_pubkey)
                        xpub_n = self.client_class.expand_path(
                            self.xpub_path[xpub])
                        txinputtype.address_n.extend(xpub_n + s)
                        txinputtype.script_type = self.get_keepkey_input_script_type(
                            txin['type'])
                    else:

                        def f(x_pubkey):
                            xpub, s = parse_xpubkey(x_pubkey)
                            return self._make_node_path(xpub, s)

                        pubkeys = list(map(f, x_pubkeys))
                        multisig = self.types.MultisigRedeemScriptType(
                            pubkeys=pubkeys,
                            signatures=map(lambda x: bfh(x)[:-1] if x else b'',
                                           txin.get('signatures')),
                            m=txin.get('num_sig'),
                        )
                        script_type = self.get_keepkey_input_script_type(
                            txin['type'])
                        txinputtype = self.types.TxInputType(
                            script_type=script_type, multisig=multisig)
                        # find which key is mine
                        for x_pubkey in x_pubkeys:
                            if is_xpubkey(x_pubkey):
                                xpub, s = parse_xpubkey(x_pubkey)
                                if xpub in self.xpub_path:
                                    xpub_n = self.client_class.expand_path(
                                        self.xpub_path[xpub])
                                    txinputtype.address_n.extend(xpub_n + s)
                                    break

                prev_hash = unhexlify(txin['prevout_hash'])
                prev_index = txin['prevout_n']

            if 'value' in txin:
                txinputtype.amount = txin['value']
            txinputtype.prev_hash = prev_hash
            txinputtype.prev_index = prev_index

            if txin.get('scriptSig') is not None:
                script_sig = bfh(txin['scriptSig'])
                txinputtype.script_sig = script_sig

            txinputtype.sequence = txin.get('sequence', 0xffffffff - 1)

            inputs.append(txinputtype)

        return inputs