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'])
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)
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)
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
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
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
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
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()