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 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)
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")
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
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()} if not self.client: raise Exception("client is None") xpub = keystore.xpub derivation = keystore.get_derivation_prefix() if not self.force_pair_with_xpub(self.client, xpub, derivation): raise Exception("Can't Pair With You Device When Sign tx") 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, _ = self.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) raise Exception("sign success")
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(): if address: return TxOutputType( amount=txout.value, script_type=OutputScriptType.PAYTOADDRESS, address=address, ) else: return TxOutputType( amount=txout.value, script_type=OutputScriptType.PAYTOOPRETURN, op_return_data= trezor_validate_op_return_output_and_get_data(txout), ) 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
def tx_outputs(self, tx: PartialTransaction, *, keystore: 'KeepKey_KeyStore'): def create_output_by_derivation(): script_type = self.get_keepkey_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 = self.types.TxOutputType(multisig=multisig, amount=txout.value, address_n=full_path, script_type=script_type) return txoutputtype def create_output_by_address(): txoutputtype = self.types.TxOutputType() txoutputtype.amount = txout.value if address: txoutputtype.script_type = self.types.PAYTOADDRESS txoutputtype.address = address else: txoutputtype.script_type = self.types.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 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
def on_otp(self, tx: PartialTransaction, otp): if not otp: self.logger.info("sign_transaction: no auth code") return otp = int(otp) long_user_id, short_id = self.get_user_id() raw_tx = serialize_tx_in_legacy_format(tx, wallet=self) try: r = server.sign(short_id, raw_tx, otp) except TrustedCoinException as e: if e.status_code == 400: # invalid OTP raise UserFacingException(_('Invalid one-time password.')) from e else: raise if r: received_raw_tx = r.get('transaction') received_tx = Transaction(received_raw_tx) tx.combine_with_other_psbt(received_tx) self.logger.info(f"twofactor: is complete {tx.is_complete()}") # reset billing_info self.billing_info = None self.plugin.start_request_thread(self)
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()
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)
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
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.BTC if constants.net.TESTNET: coin = bitbox02.btc.TBTC tx_script_type = None # Build BTCInputType list inputs = [] for txin in tx.inputs(): my_pubkey, 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" ) prev_tx = txin.utxo if prev_tx is None: raise UserFacingException(_('Missing previous tx.')) prev_inputs: List[bitbox02.BTCPrevTxInputType] = [] prev_outputs: List[bitbox02.BTCPrevTxOutputType] = [] for prev_txin in prev_tx.inputs(): prev_inputs.append( { "prev_out_hash": prev_txin.prevout.txid[::-1], "prev_out_index": prev_txin.prevout.out_idx, "signature_script": prev_txin.script_sig, "sequence": prev_txin.nsequence, } ) for prev_txout in prev_tx.outputs(): prev_outputs.append( { "value": prev_txout.value, "pubkey_script": prev_txout.scriptpubkey, } ) 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, "script_config_index": 0, "prev_tx": { "version": prev_tx.version, "locktime": prev_tx.locktime, "inputs": prev_inputs, "outputs": prev_outputs, }, } ) 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: my_pubkey, change_pubkey_path = keystore.find_my_pubkey_in_txinout(txout) outputs.append( bitbox02.BTCOutputInternal( keypath=change_pubkey_path, value=txout.value, script_config_index=0, ) ) 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, [bitbox02.btc.BTCScriptConfigWithKeypath( script_config=tx_script_type, keypath=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 serialize_tx_in_legacy_format(tx: PartialTransaction, *, wallet: Multisig_Wallet) -> str: assert isinstance(tx, PartialTransaction) # copy tx so we don't mutate the input arg # monkey-patch method of tx instance to change serialization tx = copy.deepcopy(tx) def get_siglist(txin: 'PartialTxInput', *, estimate_size=False): if txin.is_coinbase_input(): return [], [] if estimate_size: try: pubkey_size = len(txin.pubkeys[0]) except IndexError: pubkey_size = 33 # guess it is compressed num_pubkeys = max(1, len(txin.pubkeys)) pk_list = ["00" * pubkey_size] * num_pubkeys # we assume that signature will be 0x48 bytes long num_sig = max(txin.num_sig, num_pubkeys) sig_list = [ "00" * 0x48 ] * num_sig else: pk_list = ["" for pk in txin.pubkeys] for ks in wallet.get_keystores(): my_pubkey, full_path = ks.find_my_pubkey_in_txinout(txin) x_pubkey = get_xpubkey(ks, full_path[-2], full_path[-1]) pubkey_index = txin.pubkeys.index(my_pubkey) pk_list[pubkey_index] = x_pubkey assert all(pk_list) sig_list = [txin.part_sigs.get(pubkey, NO_SIGNATURE).hex() for pubkey in txin.pubkeys] return pk_list, sig_list def input_script(self, txin: PartialTxInput, *, estimate_size=False) -> str: assert estimate_size is False pubkeys, sig_list = get_siglist(txin, estimate_size=estimate_size) script = ''.join(push_script(x) for x in sig_list) if txin.script_type == 'p2sh': # put op_0 before script script = '00' + script redeem_script = multisig_script(pubkeys, txin.num_sig) script += push_script(redeem_script) return script elif txin.script_type == 'p2wsh': return '' raise Exception(f"unexpected type {txin.script_type}") tx.input_script = input_script.__get__(tx, PartialTransaction) def serialize_witness(self, txin: PartialTxInput, *, estimate_size=False): assert estimate_size is False if txin.witness is not None: return txin.witness.hex() if txin.is_coinbase_input(): return '' assert isinstance(txin, PartialTxInput) if not self.is_segwit_input(txin): return '00' pubkeys, sig_list = get_siglist(txin, estimate_size=estimate_size) if txin.script_type == 'p2wsh': witness_script = multisig_script(pubkeys, txin.num_sig) witness = construct_witness([0] + sig_list + [witness_script]) else: raise Exception(f"unexpected type {txin.script_type}") if txin.is_complete() or estimate_size: partial_format_witness_prefix = '' else: input_value = int_to_hex(txin.value_sats(), 8) witness_version = int_to_hex(0, 2) partial_format_witness_prefix = var_int(0xffffffff) + input_value + witness_version return partial_format_witness_prefix + witness tx.serialize_witness = serialize_witness.__get__(tx, PartialTransaction) buf = ELECTRUM_PARTIAL_TXN_HEADER_MAGIC.hex() buf += PARTIAL_FORMAT_VERSION.hex() buf += tx.serialize_to_network() return buf
witness_script = compile( [expiry, 'OP_CHECKLOCKTIMEVERIFY', 'OP_DROP', pubkey, 'OP_CHECKSIG']) script_hash = b2x(sha256(x(witness_script))) hodl_address = bech32_encode(script_hash) prevout = TxOutpoint(txid=txid, out_idx=vout) txin = PartialTxInput(prevout=prevout) txin._trusted_value_sats = sats txin.nsequence = sequence txin.script_sig = x(compile([])) # empty script (important!) txin.witness_script = x(witness_script) # Build the Transaction Output txout = PartialTxOutput.from_address_and_value(address, sats_less_fees) # Build and sign the transaction tx = PartialTransaction.from_io([txin], [txout], locktime=locktime) tx.version = 1 txin_index = 0 sig = tx.sign_txin(txin_index, privkey) # Prepend number of elements in script per the spec. script = [sig, witness_script] size = bytes([len(script)]) txin.witness = size + x(compile(script)) # Get the serialized txn and compute txid txn = tx.serialize() # Display results print("PayTo:", hodl_address) print("wif:", wif)
def __init__(self): PartialTransaction.__init__(self)
def is_any_tx_output_on_change_branch(tx: PartialTransaction) -> bool: return any([txout.is_change for txout in tx.outputs()])
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)