Esempio n. 1
0
 def input_script(self, txin, estimate_size=False, sign_schnorr=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'])
Esempio n. 2
0
 def tableView_didSelectRowAtIndexPath_(self, tv, indexPath):
     tv.deselectRowAtIndexPath_animated_(indexPath, True)
     parent = gui.ElectrumGui.gui
     if parent.wallet is None:
         return
     if not self.vc:
         utils.NSLog(
             "TxHistoryHelper: No self.vc defined, cannot proceed to tx detail screen"
         )
         return
     tx = None
     try:
         entry = _GetTxs(self)[indexPath.row]
         if entry.tx:
             tx = entry.tx
         else:
             tx = parent.wallet.transactions.get(entry.tx_hash, None)
             if tx and tx.raw: tx = Transaction(tx.raw)
     except:
         return
     if tx is None:
         # I'm not sure why this would happen but we did get issue #810 where it happened to 1 user.
         # Perhaps a chain split led to an "old" history view on-screen.  That's my theory, at least. -Calin
         parent.show_error(_(
             "The requested transaction has dropped out of the wallet history.\n\nIf this problem persists, please contact us at oregano.org."
         ),
                           title=_("Transaction Not Found"),
                           onOk=lambda: parent.refresh_components('history')
                           )
         return
     txd = txdetail.CreateTxDetailWithEntry(entry, tx=tx)
     self.vc.navigationController.pushViewController_animated_(txd, True)
Esempio n. 3
0
    def showTransaction_desc_(self, txraw, desc) -> None:
        tx = Transaction(txraw, sign_schnorr=parent().prefs_use_schnorr)
        tx.deserialize()
        tx_hash, status_, label_, can_broadcast, amount, fee, height, conf, timestamp, exp_n = wallet().get_tx_info(tx)
        #print("send: status_",status_,"label_",label_,"amount",amount,"conf",conf)
        size = tx.estimated_size()
        conf = 0 if conf is None else conf
        timestamp = time.time() if timestamp is None else timestamp
        status, status_str = (status_, _("Unsigned")) #wallet().get_tx_status(tx_hash, height, conf, timestamp)
        doFX = fx() and fx().is_enabled()
        ccy = fx().get_currency() if doFX else None
        fiat_amount_str = str(self.fiat.text) if doFX else None
        #HistoryEntry = namedtuple("HistoryEntry", "tx tx_hash status_str label v_str balance_str date ts conf status value fiat_amount fiat_balance fiat_amount_str fiat_balance_str ccy status_image")
        entry = HistoryEntry(tx,tx_hash,status_str,str(desc),self.amt.text,"",timestamp_to_datetime(time.time() if conf <= 0 else timestamp),timestamp,conf,status,amount,None,None,fiat_amount_str,None,ccy,None)
        def newLabel(l):
            self.descDel.text = l

        self.navigationController.pushViewController_animated_(txdetail.CreateTxDetailWithEntry(entry, on_label = newLabel), True)
Esempio n. 4
0
 def _synch_full(self, wallet):
     c = self._get_contacts(wallet)
     if not c:
         # short-circuit abort function early if no contacts exist.
         return dict()
     h = self._get_history(wallet)
     seen = dict()  # Address -> dict of tx_hash_str -> hitem tuple
     for hitem in h:
         if self.stopFlag.is_set():
             # early return, another thread requested a stop
             return None
         # loop through ALL the history and see if relevant tx's exist for contacts we care about
         tx_hash = hitem[0]
         tx = wallet.transactions.get(tx_hash)
         if tx and tx.raw:
             tx = Transaction(tx.raw)  # take a copy
             ins = tx.inputs()  # implicit deserialize
             for x in ins:
                 xa = x['address']
                 if isinstance(xa, PublicKey):
                     xa = xa.toAddress()
                 if isinstance(xa, Address) and xa in c:
                     dct = seen.get(xa, dict())
                     wasEmpty = not dct
                     if tx_hash not in dct:
                         dct[tx_hash] = hitem
                         if wasEmpty: seen[xa] = dct
             outs = tx.outputs()
             for x in outs:
                 typ, xa, dummy = x
                 if isinstance(xa, Address) and xa in c:
                     dct = seen.get(xa, dict())
                     wasEmpty = not dct
                     if tx_hash not in dct:
                         dct[tx_hash] = hitem
                         if wasEmpty: seen[xa] = dct
     storable = dict()
     for addr, d in seen.items():
         addrstr = addr.to_storage_string()
         storable[addrstr] = d
     return storable
Esempio n. 5
0
 def make_unsigned_transaction(self, amount, fee, all_inputs, outputs,
                               changes):
     ''' make unsigned transaction '''
     dust = self.dust_threshold(
     )  # always 546 for now, but this call is here in case something more sophisticated happens in the future
     coins = {}
     tx_inputs = []
     amounts = {}
     try:
         for player in all_inputs:
             inputs_coins = self.get_coins(all_inputs[player])
             # if there are no coins on input it terminates the process
             if inputs_coins:
                 coins[player] = inputs_coins
             else:
                 return None
     except BaseException as e:
         self.print_error('make_unsigned_transaction:', repr(e))
         return None
     for player, pubkey_utxos in coins.items():
         amounts[player] = 0
         for pubkey, utxos in pubkey_utxos.items():
             for utxo in utxos:
                 utxo['type'] = 'p2pkh'
                 utxo['address'] = Address.from_pubkey(pubkey)
                 utxo['pubkeys'] = [pubkey]
                 utxo['x_pubkeys'] = [pubkey]
                 utxo['prevout_hash'] = utxo['tx_hash']
                 utxo['prevout_n'] = utxo['tx_pos']
                 utxo['signatures'] = [None]
                 utxo['num_sig'] = 1
                 tx_inputs.append(utxo)
                 amounts[player] += utxo['value']
     tx_inputs.sort(key=lambda x: x['prevout_hash'] + str(x["tx_pos"]))
     tx_outputs = [(TYPE_ADDRESS, Address.from_string(output), int(amount))
                   for output in outputs]
     transaction = Transaction.from_io(tx_inputs,
                                       tx_outputs,
                                       sign_schnorr=False)
     tx_changes = [
         (TYPE_ADDRESS, Address.from_string(changes[player]),
          int(amounts[player] - amount - fee)) for player in sorted(changes)
         if Address.is_valid(changes[player]) and int(amounts[player] -
                                                      amount - fee) >= dust
     ]
     transaction.add_outputs(tx_changes)
     return transaction
Esempio n. 6
0
def tx_from_components(all_components, session_hash):
    """ Returns the tx and a list of indices matching inputs with components"""
    input_indices = []
    assert len(session_hash) == 32
    if Protocol.FUSE_ID is None:
        prefix = []
    else:
        assert len(Protocol.FUSE_ID) == 4
        prefix = [4, *Protocol.FUSE_ID]
    inputs = []
    outputs = [
        (TYPE_SCRIPT,
         ScriptOutput(bytes([OpCodes.OP_RETURN, *prefix, 32]) + session_hash),
         0)
    ]
    for i, compser in enumerate(all_components):
        comp = pb.Component()
        comp.ParseFromString(compser)
        ctype = comp.WhichOneof('component')
        if ctype == 'input':
            inp = comp.input
            if len(inp.prev_txid) != 32:
                raise FusionError("bad component prevout")
            inputs.append(
                dict(address=Address.from_P2PKH_hash(hash160(inp.pubkey)),
                     prevout_hash=inp.prev_txid[::-1].hex(),
                     prevout_n=inp.prev_index,
                     num_sig=1,
                     signatures=[None],
                     type='p2pkh',
                     x_pubkeys=[inp.pubkey.hex()],
                     pubkeys=[inp.pubkey.hex()],
                     sequence=0xffffffff,
                     value=inp.amount))
            input_indices.append(i)
        elif ctype == 'output':
            out = comp.output
            atype, addr = get_address_from_output_script(out.scriptpubkey)
            if atype != TYPE_ADDRESS:
                raise FusionError("bad component address")
            outputs.append((TYPE_ADDRESS, addr, out.amount))
        elif ctype != 'blank':
            raise FusionError("bad component")
    tx = Transaction.from_io(inputs, outputs, locktime=0, sign_schnorr=True)
    tx.version = 1
    return tx, input_indices
Esempio n. 7
0
 def _build_history_entry(h_item, statusImagesOverride, forceNoFX):
     sImages = StatusImages if not statusImagesOverride or len(
         statusImagesOverride) < len(StatusImages) else statusImagesOverride
     parent = gui.ElectrumGui.gui
     wallet = parent.wallet
     daemon = parent.daemon
     if wallet is None or daemon is None:
         utils.NSLog(
             "buid_history_entry: wallet and/or daemon was None, returning early"
         )
         return None
     fx = daemon.fx if daemon.fx and daemon.fx.show_history() else None
     ccy = ''
     tx_hash, height, conf, timestamp, value, balance = h_item
     status, status_str = wallet.get_tx_status(tx_hash, height, conf,
                                               timestamp)
     has_invoice = wallet.invoices.paid.get(tx_hash)
     v_str = parent.format_amount(value, True, whitespaces=True)
     balance_str = parent.format_amount(balance, whitespaces=True)
     label = wallet.get_label(tx_hash)
     date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)
     ts = timestamp if conf > 0 else time.time()
     fiat_amount = 0
     fiat_balance = 0
     fiat_amount_str = ''
     fiat_balance_str = ''
     if fx: fx.history_used_spot = False
     if not forceNoFX and fx:
         if not ccy:
             ccy = fx.get_currency()
         try:
             hdate = timestamp_to_datetime(
                 time.time() if conf <= 0 else timestamp)
             hamount = fx.historical_value(value, hdate)
             htext = fx.historical_value_str(value,
                                             hdate) if hamount else ''
             fiat_amount = hamount if hamount else fiat_amount
             fiat_amount_str = htext if htext else fiat_amount_str
             hamount = fx.historical_value(balance, hdate) if balance else 0
             htext = fx.historical_value_str(balance,
                                             hdate) if hamount else ''
             fiat_balance = hamount if hamount else fiat_balance
             fiat_balance_str = htext if htext else fiat_balance_str
         except:
             utils.NSLog(
                 "Exception in get_history computing fiat amounts!\n%s",
                 str(sys.exc_info()[1]))
             #import traceback
             #traceback.print_exc(file=sys.stderr)
             fiat_amount = fiat_balance = 0
             fiat_amount_str = fiat_balance_str = ''
     if status >= 0 and status < len(sImages):
         img = sImages[status]
     else:
         img = None
     tx = wallet.transactions.get(tx_hash, None)
     if tx is not None and tx.raw:
         # NB: save a copy of the tx in this hentry, because it may get
         # deserialized later, and if we were to deserialize the tx that's
         # in the wallet dict, we'd eat memory.
         tx = Transaction(tx.raw)
     entry = HistoryEntry(tx, tx_hash, status_str, label, v_str,
                          balance_str, date, ts, conf, status, value,
                          fiat_amount, fiat_balance, fiat_amount_str,
                          fiat_balance_str, ccy, img)
     return entry
Esempio n. 8
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()