示例#1
0
 def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx):
     self.prev_tx = prev_tx
     client = self.get_client(keystore)
     inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore)
     outputs = self.tx_outputs(tx, keystore=keystore)
     signatures = client.sign_tx(self.get_coin_name(), inputs, outputs,
                                 lock_time=tx.locktime, version=tx.version)[0]
     signatures = [(bh2u(x) + '01') for x in signatures]
     tx.update_signatures(signatures)
示例#2
0
 def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx):
     prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items() }
     client = self.get_client(keystore)
     inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore)
     outputs = self.tx_outputs(tx, keystore=keystore)
     details = SignTx(lock_time=tx.locktime, version=tx.version)
     signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx)
     signatures = [(bh2u(x) + '01') for x in signatures]
     tx.update_signatures(signatures)
示例#3
0
 def _add_info_to_tx_from_wallet_and_network(self, tx: PartialTransaction) -> bool:
     """Returns whether successful."""
     # note side-effect: tx is being mutated
     assert isinstance(tx, PartialTransaction)
     try:
         # note: this might download input utxos over network
         # FIXME network code in gui thread...
         tx.add_info_from_wallet(self.wallet, ignore_network_issues=False)
     except NetworkException as e:
         self.app.show_error(repr(e))
         return False
     return True
示例#4
0
 def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey,
                                remote_signature, pubkey, privkey):
     assert type(remote_pubkey) is bytes
     assert len(remote_pubkey) == 33
     assert type(remote_signature) is str
     assert type(pubkey) is bytes
     assert type(privkey) is bytes
     assert len(pubkey) == 33
     assert len(privkey) == 33
     tx.sign({bh2u(pubkey): (privkey[:-1], True)})
     tx.add_signature_to_txin(txin_idx=0,
                              signing_pubkey=remote_pubkey.hex(),
                              sig=remote_signature + "01")
示例#5
0
    def tx_outputs(self, tx: PartialTransaction, *,
                   keystore: 'TrezorKeyStore'):
        def create_output_by_derivation():
            script_type = self.get_trezor_output_script_type(txout.script_type)
            if len(txout.pubkeys) > 1:
                xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(
                    tx, txout)
                multisig = self._make_multisig(txout.num_sig,
                                               xpubs_and_deriv_suffixes)
            else:
                multisig = None
            my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout)
            assert full_path
            txoutputtype = TxOutputType(multisig=multisig,
                                        amount=txout.value,
                                        address_n=full_path,
                                        script_type=script_type)
            return txoutputtype

        def create_output_by_address():
            txoutputtype = TxOutputType()
            txoutputtype.amount = txout.value
            if address:
                txoutputtype.script_type = OutputScriptType.PAYTOADDRESS
                txoutputtype.address = address
            else:
                txoutputtype.script_type = OutputScriptType.PAYTOOPRETURN
                txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(
                    txout)
            return txoutputtype

        outputs = []
        has_change = False
        any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)

        for txout in tx.outputs():
            address = txout.address
            use_create_by_derivation = False

            if txout.is_mine and not has_change:
                # prioritise hiding outputs on the 'change' branch from user
                # because no more than one change address allowed
                # note: ^ restriction can be removed once we require fw
                # that has https://github.com/trezor/trezor-mcu/pull/306
                if txout.is_change == any_output_on_change_branch:
                    use_create_by_derivation = True
                    has_change = True

            if use_create_by_derivation:
                txoutputtype = create_output_by_derivation()
            else:
                txoutputtype = create_output_by_address()
            outputs.append(txoutputtype)

        return outputs
示例#6
0
 def do_dscancel(self):
     from .dscancel_dialog import DSCancelDialog
     tx = self.tx
     txid = tx.txid()
     assert txid
     if not isinstance(tx, PartialTransaction):
         tx = PartialTransaction.from_tx(tx)
     if not self._add_info_to_tx_from_wallet_and_network(tx):
         return
     fee = tx.get_fee()
     assert fee is not None
     size = tx.estimated_size()
     cb = partial(self._do_dscancel, tx=tx)
     d = DSCancelDialog(self.app, fee, size, cb)
     d.open()
示例#7
0
    def sign_transaction(self, tx, password):
        # Upload PSBT for signing.
        # - we can also work offline (without paired device present)
        if tx.is_complete():
            return

        client = self.get_client()

        assert client.dev.master_fingerprint == self.get_xfp_int()

        raw_psbt = tx.serialize_as_bytes()

        try:
            try:
                self.handler.show_message("Authorize Transaction...")

                client.sign_transaction_start(raw_psbt)

                while 1:
                    # How to kill some time, without locking UI?
                    time.sleep(0.250)

                    resp = client.sign_transaction_poll()
                    if resp is not None:
                        break

                rlen, rsha = resp
            
                # download the resulting txn.
                raw_resp = client.download_file(rlen, rsha)

            finally:
                self.handler.finished()

        except (CCUserRefused, CCBusyError) as exc:
            self.logger.info(f'Did not sign: {exc}')
            self.handler.show_error(str(exc))
            return
        except BaseException as e:
            self.logger.exception('')
            self.give_error(e, True)
            return

        tx2 = PartialTransaction.from_raw_psbt(raw_resp)
        # apply partial signatures back into txn
        tx.combine_with_other_psbt(tx2)
示例#8
0
    def sign_transaction(self, tx: PartialTransaction, password: str):
        if tx.is_complete():
            return
        client = self.get_client()
        assert isinstance(client, BitBox02Client)

        try:
            try:
                self.handler.show_message("Authorize Transaction...")
                client.sign_transaction(self, tx, self.handler.get_wallet())

            finally:
                self.handler.finished()

        except Exception as e:
            self.logger.exception("")
            self.give_error(e, True)
            return
示例#9
0
    def sign_transaction(
        self,
        keystore: Hardware_KeyStore,
        tx: PartialTransaction,
        wallet: Deterministic_Wallet,
    ):
        if tx.is_complete():
            return

        if self.bitbox02_device is None:
            raise Exception(
                "Need to setup communication first before attempting any BitBox02 calls"
            )

        coin = bitbox02.btc.LTC
        if constants.net.TESTNET:
            coin = bitbox02.btc.TLTC

        tx_script_type = None

        # Build BTCInputType list
        inputs = []
        for txin in tx.inputs():
            _, full_path = keystore.find_my_pubkey_in_txinout(txin)

            if full_path is None:
                raise Exception(
                    "A wallet owned pubkey was not found in the transaction input to be signed"
                )

            inputs.append(
                {
                    "prev_out_hash": txin.prevout.txid[::-1],
                    "prev_out_index": txin.prevout.out_idx,
                    "prev_out_value": txin.value_sats(),
                    "sequence": txin.nsequence,
                    "keypath": full_path,
                }
            )

            if tx_script_type == None:
                tx_script_type = txin.script_type
            elif tx_script_type != txin.script_type:
                raise Exception("Cannot mix different input script types")

        if tx_script_type == "p2wpkh":
            tx_script_type = bitbox02.btc.BTCScriptConfig(
                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
            )
        elif tx_script_type == "p2wpkh-p2sh":
            tx_script_type = bitbox02.btc.BTCScriptConfig(
                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
            )
        elif tx_script_type == "p2wsh":
            if type(wallet) is Multisig_Wallet:
                tx_script_type = self.btc_multisig_config(coin, full_path, wallet)
            else:
                raise Exception("Can only use p2wsh with multisig wallets")
        else:
            raise UserFacingException(
                "invalid input script type: {} is not supported by the BitBox02".format(
                    tx_script_type
                )
            )

        # Build BTCOutputType list
        outputs = []
        for txout in tx.outputs():
            assert txout.address
            # check for change
            if txout.is_change:
                _, change_pubkey_path = keystore.find_my_pubkey_in_txinout(txout)
                outputs.append(
                    bitbox02.BTCOutputInternal(
                        keypath=change_pubkey_path, value=txout.value,
                    )
                )
            else:
                addrtype, pubkey_hash = bitcoin.address_to_hash(txout.address)
                if addrtype == OnchainOutputType.P2PKH:
                    output_type = bitbox02.btc.P2PKH
                elif addrtype == OnchainOutputType.P2SH:
                    output_type = bitbox02.btc.P2SH
                elif addrtype == OnchainOutputType.WITVER0_P2WPKH:
                    output_type = bitbox02.btc.P2WPKH
                elif addrtype == OnchainOutputType.WITVER0_P2WSH:
                    output_type = bitbox02.btc.P2WSH
                else:
                    raise UserFacingException(
                        "Received unsupported output type during transaction signing: {} is not supported by the BitBox02".format(
                            addrtype
                        )
                    )
                outputs.append(
                    bitbox02.BTCOutputExternal(
                        output_type=output_type,
                        output_hash=pubkey_hash,
                        value=txout.value,
                    )
                )

        if type(wallet) is Standard_Wallet:
            keypath_account = full_path[:3]
        elif type(wallet) is Multisig_Wallet:
            keypath_account = full_path[:4]
        else:
            raise Exception(
                "BitBox02 does not support this wallet type: {}".format(type(wallet))
            )

        sigs = self.bitbox02_device.btc_sign(
            coin,
            tx_script_type,
            keypath_account=keypath_account,
            inputs=inputs,
            outputs=outputs,
            locktime=tx.locktime,
            version=tx.version,
        )

        # Fill signatures
        if len(sigs) != len(tx.inputs()):
            raise Exception("Incorrect number of inputs signed.")  # Should never occur
        signatures = [bh2u(ecc.der_sig_from_sig_string(x[1])) + "01" for x in sigs]
        tx.update_signatures(signatures)
示例#10
0
def is_any_tx_output_on_change_branch(tx: PartialTransaction) -> bool:
    return any([txout.is_change for txout in tx.outputs()])
示例#11
0
    def __init__(
            self,
            *,
            main_window: 'ElectrumWindow',
            tx: PartialTransaction,
            txid: str,
            title: str,
            help_text: str,
    ):
        WindowModalDialog.__init__(self, main_window, title=title)
        self.window = main_window
        self.wallet = main_window.wallet
        self.tx = tx
        assert txid
        self.txid = txid

        fee = tx.get_fee()
        assert fee is not None
        tx_size = tx.estimated_size()
        old_fee_rate = fee / tx_size  # sat/vbyte
        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel(help_text))

        ok_button = OkButton(self)
        self.adv_button = QPushButton(_("Show advanced settings"))
        warning_label = WWLabel('\n')
        warning_label.setStyleSheet(ColorScheme.RED.as_stylesheet())
        self.feerate_e = FeerateEdit(lambda: 0)
        self.feerate_e.setAmount(max(old_fee_rate * 1.5, old_fee_rate + 1))

        def on_feerate():
            fee_rate = self.feerate_e.get_amount()
            warning_text = '\n'
            if fee_rate is not None:
                try:
                    new_tx = self.rbf_func(fee_rate)
                except Exception as e:
                    new_tx = None
                    warning_text = str(e).replace('\n', ' ')
            else:
                new_tx = None
            ok_button.setEnabled(new_tx is not None)
            warning_label.setText(warning_text)

        self.feerate_e.textChanged.connect(on_feerate)

        def on_slider(dyn, pos, fee_rate):
            fee_slider.activate()
            if fee_rate is not None:
                self.feerate_e.setAmount(fee_rate / 1000)

        fee_slider = FeeSlider(self.window, self.window.config, on_slider)
        fee_combo = FeeComboBox(fee_slider)
        fee_slider.deactivate()
        self.feerate_e.textEdited.connect(fee_slider.deactivate)

        grid = QGridLayout()
        grid.addWidget(QLabel(_('Current Fee') + ':'), 0, 0)
        grid.addWidget(QLabel(self.window.format_amount(fee) + ' ' + self.window.base_unit()), 0, 1)
        grid.addWidget(QLabel(_('Current Fee rate') + ':'), 1, 0)
        grid.addWidget(QLabel(self.window.format_fee_rate(1000 * old_fee_rate)), 1, 1)
        grid.addWidget(QLabel(_('New Fee rate') + ':'), 2, 0)
        grid.addWidget(self.feerate_e, 2, 1)
        grid.addWidget(fee_slider, 3, 1)
        grid.addWidget(fee_combo, 3, 2)
        vbox.addLayout(grid)
        self._add_advanced_options_cont(vbox)
        vbox.addWidget(warning_label)

        btns_hbox = QHBoxLayout()
        btns_hbox.addWidget(self.adv_button)
        btns_hbox.addStretch(1)
        btns_hbox.addWidget(CancelButton(self))
        btns_hbox.addWidget(ok_button)
        vbox.addLayout(btns_hbox)