Exemple #1
0
        def update(features):
            self.features = features
            set_label_enabled()
            bl_hash = bh2u(features.bootloader_hash)
            bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
            noyes = [_("No"), _("Yes")]
            endis = [_("Enable Passphrases"), _("Disable Passphrases")]
            disen = [_("Disabled"), _("Enabled")]
            setchange = [_("Set a PIN"), _("Change PIN")]

            version = "%d.%d.%d" % (features.major_version,
                                    features.minor_version,
                                    features.patch_version)
            coins = ", ".join(coin.coin_name for coin in features.coins)

            device_label.setText(features.label)
            pin_set_label.setText(noyes[features.pin_protection])
            passphrases_label.setText(disen[features.passphrase_protection])
            bl_hash_label.setText(bl_hash)
            label_edit.setText(features.label)
            device_id_label.setText(features.device_id)
            initialized_label.setText(noyes[features.initialized])
            version_label.setText(version)
            coins_label.setText(coins)
            clear_pin_button.setVisible(features.pin_protection)
            clear_pin_warning.setVisible(features.pin_protection)
            pin_button.setText(setchange[features.pin_protection])
            pin_msg.setVisible(not features.pin_protection)
            passphrase_button.setText(endis[features.passphrase_protection])
            language_label.setText(features.language)
Exemple #2
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])
Exemple #3
0
 def test_mnemonic_to_seed_basic(self):
     # note: not a valid actilectrum seed
     seed = mnemonic.Mnemonic.mnemonic_to_seed(mnemonic='foobar',
                                               passphrase='none')
     self.assertEqual(
         '741b72fd15effece6bfe5a26a52184f66811bd2be363190e07a42cca442b1a5bb22b3ad0eb338197287e6d314866c7fba863ac65d3f156087a5052ebc7157fce',
         bh2u(seed))
 def format_fields(self, chan):
     labels = {}
     for subject in (REMOTE, LOCAL):
         bal_minus_htlcs = chan.balance_minus_outgoing_htlcs(
             subject) // 1000
         label = self.parent.format_amount(bal_minus_htlcs)
         other = subject.inverted()
         bal_other = chan.balance(other) // 1000
         bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs(
             other) // 1000
         if bal_other != bal_minus_htlcs_other:
             label += ' (+' + self.parent.format_amount(
                 bal_other - bal_minus_htlcs_other) + ')'
         labels[subject] = label
     status = chan.get_state_for_GUI()
     closed = chan.is_closed()
     if self.parent.network.is_lightning_running():
         node_info = self.lnworker.channel_db.get_node_info_for_node_id(
             chan.node_id)
         node_alias = (node_info.alias if node_info else '') or ''
     else:
         node_alias = ''
     return [
         chan.short_id_for_GUI(),
         bh2u(chan.node_id), node_alias, '' if closed else labels[LOCAL],
         '' if closed else labels[REMOTE], status
     ]
Exemple #5
0
 def gen_random_versioned_seed(cls):
     version = cls.LATEST_VERSION
     hex_seed = bh2u(os.urandom(16))
     checksum = cls.code_hashid(version + hex_seed)
     return VersionedSeed(version=version.upper(),
                          seed=hex_seed.upper(),
                          checksum=checksum.upper())
Exemple #6
0
 def test_mnemonic_to_seed(self):
     for test_name, test in SEED_TEST_CASES.items():
         if test.words_hex is not None:
             self.assertEqual(test.words_hex,
                              bh2u(test.words.encode('utf8')),
                              msg=test_name)
         self.assertTrue(is_new_seed(test.words, prefix=test.seed_version),
                         msg=test_name)
         m = mnemonic.Mnemonic(lang=test.lang)
         if test.entropy is not None:
             self.assertEqual(test.entropy,
                              m.mnemonic_decode(test.words),
                              msg=test_name)
         if test.passphrase_hex is not None:
             self.assertEqual(test.passphrase_hex,
                              bh2u(test.passphrase.encode('utf8')),
                              msg=test_name)
         seed = mnemonic.Mnemonic.mnemonic_to_seed(
             mnemonic=test.words, passphrase=test.passphrase)
         self.assertEqual(test.bip32_seed, bh2u(seed), msg=test_name)
 def open(self, *args, **kwargs):
     super(LightningOpenChannelDialog, self).open(*args, **kwargs)
     if self.lnaddr:
         fee = self.app.electrum_config.fee_per_kb()
         if not fee:
             fee = config.FEERATE_FALLBACK_STATIC_FEE
         self.amount = self.app.format_amount_and_units(self.lnaddr.amount *
                                                        COIN + fee * 2)
         self.pubkey = bh2u(self.lnaddr.pubkey.serialize())
     if self.msg:
         self.app.show_info(self.msg)
 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)
 def make_htlc_item(self, i: UpdateAddHtlc,
                    direction: Direction) -> HTLCItem:
     it = HTLCItem(
         _('Sent HTLC with ID {}' if Direction.SENT ==
           direction else 'Received HTLC with ID {}').format(i.htlc_id))
     it.appendRow(
         [HTLCItem(_('Amount')),
          HTLCItem(self.format_msat(i.amount_msat))])
     it.appendRow(
         [HTLCItem(_('CLTV expiry')),
          HTLCItem(str(i.cltv_expiry))])
     it.appendRow(
         [HTLCItem(_('Payment hash')),
          HTLCItem(bh2u(i.payment_hash))])
     return it
 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)
Exemple #11
0
def build_psbt(tx: Transaction, wallet: Abstract_Wallet):
    # Render a PSBT file, for possible upload to Coldcard.
    #
    # TODO this should be part of Wallet object, or maybe Transaction?

    if getattr(tx, 'raw_psbt', False):
        _logger.info('PSBT cache hit')
        return tx.raw_psbt

    inputs = tx.inputs()
    if 'prev_tx' not in inputs[0]:
        # fetch info about inputs, if needed?
        # - needed during export PSBT flow, not normal online signing
        wallet.add_hw_info(tx)

    # wallet.add_hw_info installs this attr
    assert tx.output_info is not None, 'need data about outputs'

    # Build a map of all pubkeys needed as derivation from master XFP, in PSBT binary format
    # 1) binary version of the common subpath for all keys
    #       m/ => fingerprint LE32
    #       a/b/c => ints
    #
    # 2) all used keys in transaction:
    #    - for all inputs and outputs (when its change back)
    #    - for all keystores, if multisig
    #
    subkeys = {}
    for ks in wallet.get_keystores():

        # XFP + fixed prefix for this keystore
        ks_prefix = packed_xfp_path_for_keystore(ks)

        # all pubkeys needed for input signing
        for xpubkey, derivation in ks.get_tx_derivations(tx).items():
            pubkey = xpubkey_to_pubkey(xpubkey)

            # assuming depth two, non-harded: change + index
            aa, bb = derivation
            assert 0 <= aa < 0x80000000 and 0 <= bb < 0x80000000

            subkeys[bfh(pubkey)] = ks_prefix + pack('<II', aa, bb)

        # all keys related to change outputs
        for o in tx.outputs():
            if o.address in tx.output_info:
                # this address "is_mine" but might not be change (if I send funds to myself)
                output_info = tx.output_info.get(o.address)
                if not output_info.is_change:
                    continue
                chg_path = output_info.address_index
                assert chg_path[0] == 1 and len(
                    chg_path) == 2, f"unexpected change path: {chg_path}"
                pubkey = ks.derive_pubkey(True, chg_path[1])
                subkeys[bfh(pubkey)] = ks_prefix + pack('<II', *chg_path)

    for txin in inputs:
        assert txin['type'] != 'coinbase', _("Coinbase not supported")

        if txin['type'] in ['p2sh', 'p2wsh-p2sh', 'p2wsh']:
            assert type(wallet) is Multisig_Wallet

    # Construct PSBT from start to finish.
    out_fd = io.BytesIO()
    out_fd.write(b'psbt\xff')

    def write_kv(ktype, val, key=b''):
        # serialize helper: write w/ size and key byte
        out_fd.write(my_var_int(1 + len(key)))
        out_fd.write(bytes([ktype]) + key)

        if isinstance(val, str):
            val = bfh(val)

        out_fd.write(my_var_int(len(val)))
        out_fd.write(val)

    # global section: just the unsigned txn
    class CustomTXSerialization(Transaction):
        @classmethod
        def input_script(cls, txin, estimate_size=False):
            return ''

    unsigned = bfh(
        CustomTXSerialization(
            tx.serialize()).serialize_to_network(witness=False))
    write_kv(PSBT_GLOBAL_UNSIGNED_TX, unsigned)

    if type(wallet) is Multisig_Wallet:

        # always put the xpubs into the PSBT, useful at least for checking
        for xp, ks in zip(wallet.get_master_public_keys(),
                          wallet.get_keystores()):
            ks_prefix = packed_xfp_path_for_keystore(ks)

            write_kv(PSBT_GLOBAL_XPUB, ks_prefix, DecodeBase58Check(xp))

    # end globals section
    out_fd.write(b'\x00')

    # inputs section
    for txin in inputs:
        if Transaction.is_segwit_input(txin):
            utxo = txin['prev_tx'].outputs()[txin['prevout_n']]
            spendable = txin['prev_tx'].serialize_output(utxo)
            write_kv(PSBT_IN_WITNESS_UTXO, spendable)
        else:
            write_kv(PSBT_IN_NON_WITNESS_UTXO, str(txin['prev_tx']))

        pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)

        pubkeys = [bfh(k) for k in pubkeys]

        if type(wallet) is Multisig_Wallet:
            # always need a redeem script for multisig
            scr = Transaction.get_preimage_script(txin)

            if Transaction.is_segwit_input(txin):
                # needed for both p2wsh-p2sh and p2wsh
                write_kv(PSBT_IN_WITNESS_SCRIPT, bfh(scr))
            else:
                write_kv(PSBT_IN_REDEEM_SCRIPT, bfh(scr))

        sigs = txin.get('signatures')

        for pk_pos, (pubkey, x_pubkey) in enumerate(zip(pubkeys, x_pubkeys)):
            if pubkey in subkeys:
                # faster? case ... calculated above
                write_kv(PSBT_IN_BIP32_DERIVATION, subkeys[pubkey], pubkey)
            else:
                # when an input is partly signed, tx.get_tx_derivations()
                # doesn't include that keystore's value and yet we need it
                # because we need to show a correct keypath...
                assert x_pubkey[0:2] == 'ff', x_pubkey

                for ks in wallet.get_keystores():
                    d = ks.get_pubkey_derivation(x_pubkey)
                    if d is not None:
                        ks_path = packed_xfp_path_for_keystore(ks, d)
                        write_kv(PSBT_IN_BIP32_DERIVATION, ks_path, pubkey)
                        break
                else:
                    raise AssertionError("no keystore for: %s" % x_pubkey)

            if txin['type'] == 'p2wpkh-p2sh':
                assert len(
                    pubkeys) == 1, 'can be only one redeem script per input'
                pa = hash_160(pubkey)
                assert len(pa) == 20
                write_kv(PSBT_IN_REDEEM_SCRIPT, b'\x00\x14' + pa)

            # optional? insert (partial) signatures that we already have
            if sigs and sigs[pk_pos]:
                write_kv(PSBT_IN_PARTIAL_SIG, bfh(sigs[pk_pos]), pubkey)

        out_fd.write(b'\x00')

    # outputs section
    for o in tx.outputs():
        # can be empty, but must be present, and helpful to show change inputs
        # wallet.add_hw_info() adds some data about change outputs into tx.output_info
        if o.address in tx.output_info:
            # this address "is_mine" but might not be change (if I send funds to myself)
            output_info = tx.output_info.get(o.address)
            if output_info.is_change:
                pubkeys = [bfh(i) for i in wallet.get_public_keys(o.address)]

                # Add redeem/witness script?
                if type(wallet) is Multisig_Wallet:
                    # always need a redeem script for multisig cases
                    scr = bfh(
                        multisig_script([bh2u(i) for i in sorted(pubkeys)],
                                        wallet.m))

                    if output_info.script_type == 'p2wsh-p2sh':
                        write_kv(PSBT_OUT_WITNESS_SCRIPT, scr)
                        write_kv(PSBT_OUT_REDEEM_SCRIPT,
                                 b'\x00\x20' + sha256(scr))
                    elif output_info.script_type == 'p2wsh':
                        write_kv(PSBT_OUT_WITNESS_SCRIPT, scr)
                    elif output_info.script_type == 'p2sh':
                        write_kv(PSBT_OUT_REDEEM_SCRIPT, scr)
                    else:
                        raise ValueError(output_info.script_type)

                elif output_info.script_type == 'p2wpkh-p2sh':
                    # need a redeem script when P2SH is used to wrap p2wpkh
                    assert len(pubkeys) == 1
                    pa = hash_160(pubkeys[0])
                    write_kv(PSBT_OUT_REDEEM_SCRIPT, b'\x00\x14' + pa)

                # Document change output's bip32 derivation(s)
                for pubkey in pubkeys:
                    sk = subkeys[pubkey]
                    write_kv(PSBT_OUT_BIP32_DERIVATION, sk, pubkey)

        out_fd.write(b'\x00')

    # capture for later use
    tx.raw_psbt = out_fd.getvalue()

    return tx.raw_psbt
 def mock_fork(self, bad_header):
     forkpoint = bad_header['block_height']
     b = blockchain.Blockchain(config=self.config, forkpoint=forkpoint, parent=None,
                               forkpoint_hash=bh2u(sha256(str(forkpoint))), prev_hash=bh2u(sha256(str(forkpoint-1))))
     return b
    def __init__(self, window: 'ElectrumWindow', chan_id: bytes):
        super().__init__(window)

        # initialize instance fields
        self.window = window
        chan = self.chan = window.wallet.lnworker.channels[chan_id]
        self.format_msat = lambda msat: window.format_amount_and_units(msat /
                                                                       1000)

        # connect signals with slots
        self.ln_payment_completed.connect(self.do_ln_payment_completed)
        self.ln_payment_failed.connect(self.do_ln_payment_failed)
        self.state_changed.connect(self.do_state_changed)
        self.htlc_added.connect(self.do_htlc_added)

        # register callbacks for updating
        util.register_callback(self.ln_payment_completed.emit,
                               ['ln_payment_completed'])
        util.register_callback(self.ln_payment_failed.emit,
                               ['ln_payment_failed'])
        util.register_callback(self.htlc_added.emit, ['htlc_added'])
        util.register_callback(self.state_changed.emit, ['channel'])

        # set attributes of QDialog
        self.setWindowTitle(_('Channel Details'))
        self.setMinimumSize(800, 400)

        # add layouts
        vbox = QtWidgets.QVBoxLayout(self)
        vbox.addWidget(QLabel(_('Remote Node ID:')))
        remote_id_e = ButtonsLineEdit(bh2u(chan.node_id))
        remote_id_e.addCopyButton(self.window.app)
        vbox.addWidget(remote_id_e)
        funding_label_text = f'<a href=click_destination>{chan.funding_outpoint.txid}</a>:{chan.funding_outpoint.output_index}'
        vbox.addWidget(QLabel(_('Funding Outpoint:')))
        vbox.addWidget(LinkedLabel(funding_label_text, self.show_tx))

        form_layout = QtWidgets.QFormLayout(None)
        # add form content
        form_layout.addRow(
            _('Channel ID:'),
            SelectableLabel(
                f"{chan.channel_id.hex()} (Short: {chan.short_channel_id})"))
        form_layout.addRow(_('State:'),
                           SelectableLabel(chan.get_state_for_GUI()))
        self.initiator = 'Local' if chan.constraints.is_initiator else 'Remote'
        form_layout.addRow(_('Initiator:'), SelectableLabel(self.initiator))
        self.capacity = self.window.format_amount_and_units(
            chan.constraints.capacity)
        form_layout.addRow(_('Capacity:'), SelectableLabel(self.capacity))
        self.can_send_label = SelectableLabel()
        self.can_receive_label = SelectableLabel()
        form_layout.addRow(_('Can send:'), self.can_send_label)
        form_layout.addRow(_('Can receive:'), self.can_receive_label)
        self.received_label = SelectableLabel()
        form_layout.addRow(_('Received:'), self.received_label)
        self.sent_label = SelectableLabel()
        form_layout.addRow(_('Sent:'), self.sent_label)
        #self.htlc_minimum_msat = SelectableLabel(str(chan.config[REMOTE].htlc_minimum_msat))
        #form_layout.addRow(_('Minimum HTLC value accepted by peer (mSAT):'), self.htlc_minimum_msat)
        #self.max_htlcs = SelectableLabel(str(chan.config[REMOTE].max_accepted_htlcs))
        #form_layout.addRow(_('Maximum number of concurrent HTLCs accepted by peer:'), self.max_htlcs)
        #self.max_htlc_value = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].max_htlc_value_in_flight_msat / 1000))
        #form_layout.addRow(_('Maximum value of in-flight HTLCs accepted by peer:'), self.max_htlc_value)
        self.dust_limit = SelectableLabel(
            self.window.format_amount_and_units(
                chan.config[REMOTE].dust_limit_sat))
        form_layout.addRow(_('Remote dust limit:'), self.dust_limit)
        self.remote_reserve = self.window.format_amount_and_units(
            chan.config[REMOTE].reserve_sat)
        form_layout.addRow(_('Remote reserve:'),
                           SelectableLabel(self.remote_reserve))
        vbox.addLayout(form_layout)

        # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
        vbox.addWidget(QLabel(_('Payments (HTLCs):')))
        w = QtWidgets.QTreeView(self)
        htlc_dict = chan.get_payments()
        w.setModel(self.make_model(htlc_dict))
        w.header().setSectionResizeMode(0,
                                        QtWidgets.QHeaderView.ResizeToContents)
        vbox.addWidget(w)
        vbox.addLayout(Buttons(CloseButton(self)))
        # initialize sent/received fields
        self.update()
Exemple #14
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.ACM
        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)
    def new_channel_dialog(self):
        lnworker = self.parent.wallet.lnworker
        d = WindowModalDialog(self.parent, _('Open Channel'))
        vbox = QVBoxLayout(d)
        vbox.addWidget(
            QLabel(_('Enter Remote Node ID or connection string or invoice')))
        local_nodeid = FreezableLineEdit()
        local_nodeid.setMinimumWidth(700)
        local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
        local_nodeid.setFrozen(True)
        local_nodeid.setCursorPosition(0)
        remote_nodeid = QLineEdit()
        remote_nodeid.setMinimumWidth(700)
        amount_e = BTCAmountEdit(self.parent.get_decimal_point)

        # max button
        def spend_max():
            amount_e.setFrozen(max_button.isChecked())
            if not max_button.isChecked():
                return
            make_tx = self.parent.mktx_for_open_channel('!')
            try:
                tx = make_tx(None)
            except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
                max_button.setChecked(False)
                amount_e.setFrozen(False)
                self.main_window.show_error(str(e))
                return
            amount = tx.output_value()
            amount = min(amount, LN_MAX_FUNDING_SAT)
            amount_e.setAmount(amount)

        max_button = EnterButton(_("Max"), spend_max)
        max_button.setFixedWidth(100)
        max_button.setCheckable(True)
        suggest_button = QPushButton(d, text=_('Suggest'))
        suggest_button.clicked.connect(lambda: remote_nodeid.setText(
            bh2u(lnworker.suggest_peer() or b'')))
        clear_button = QPushButton(d, text=_('Clear'))

        def on_clear():
            amount_e.setText('')
            remote_nodeid.setText('')
            max_button.setChecked(False)

        clear_button.clicked.connect(on_clear)
        h = QGridLayout()
        h.addWidget(QLabel(_('Your Node ID')), 0, 0)
        h.addWidget(local_nodeid, 0, 1, 1, 3)
        h.addWidget(QLabel(_('Remote Node ID')), 1, 0)
        h.addWidget(remote_nodeid, 1, 1, 1, 3)
        h.addWidget(suggest_button, 2, 1)
        h.addWidget(clear_button, 2, 2)
        h.addWidget(QLabel('Amount'), 3, 0)
        h.addWidget(amount_e, 3, 1)
        h.addWidget(max_button, 3, 2)
        vbox.addLayout(h)
        ok_button = OkButton(d)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(d), ok_button))
        if not d.exec_():
            return
        if max_button.isChecked(
        ) and amount_e.get_amount() < LN_MAX_FUNDING_SAT:
            # if 'max' enabled and amount is strictly less than max allowed,
            # that means we have fewer coins than max allowed, and hence we can
            # spend all coins
            funding_sat = '!'
        else:
            funding_sat = amount_e.get_amount()
        connect_str = str(remote_nodeid.text()).strip()
        if not connect_str or not funding_sat:
            return
        self.parent.open_channel(connect_str, funding_sat, 0)