コード例 #1
0
 def show_send_to_cosigner_button(self, account: AbstractAccount,
                                  tx: Transaction) -> bool:
     if tx.is_complete() or account.can_sign(tx):
         return False
     account_id = account.get_id()
     return any(
         self._is_theirs(account_id, item, tx) for item in self._items)
コード例 #2
0
ファイル: cosigner_pool.py プロジェクト: wagnehc/electrumsv
 def show_send_to_cosigner_button(self, window: 'ElectrumWindow', account: AbstractAccount,
         tx: Transaction) -> bool:
     if window.network is None:
         return False
     if tx.is_complete() or account.can_sign(tx):
         return False
     account_id = account.get_id()
     return any(self._is_theirs(window, account_id, item, tx) for item in self._items)
コード例 #3
0
ファイル: rpc.py プロジェクト: kyuupichan/electrumsv
    def broadcast_transaction(self, tx_hex: Optional[str]=None, wallet_name: Optional[str]=None,
                              wallet_memo: Optional[str]=None) -> str:
        wallet = None
        if wallet_name and wallet_memo:
            wallet = self._get_wallet(wallet_name)

        tx = Transaction(tx_hex)
        try:
            tx_id = app_state.daemon.network.broadcast_transaction_and_wait(tx)
        except aiorpcx.jsonrpc.RPCError as e:
            if e.code == 1 and e.message.find("too-long-mempool-chain") != -1:
                return jsonrpclib.Fault(100, "too-long-mempool-chain")
            print("raising rpc error", e.code, e.message)
            raise e
        if tx.is_complete() and wallet_name and wallet_memo:
            wallet.set_label(tx_id, wallet_memo)
        return tx_id
コード例 #4
0
    def sign_transaction(self, tx: Transaction, password: str,
                         tx_context: TransactionContext) -> None:
        if tx.is_complete():
            return

        assert not len(tx_context.prev_txs
                       ), "This keystore does not require input transactions"
        # path of the xpubs that are involved
        xpub_path: Dict[str, str] = {}
        for txin in tx.inputs:
            for x_pubkey in txin.x_pubkeys:
                if not x_pubkey.is_bip32_key():
                    continue
                xpub = x_pubkey.bip32_extended_key()
                if xpub == self.get_master_public_key():
                    xpub_path[xpub] = self.get_derivation()

        assert self.plugin is not None
        self.plugin.sign_transaction(self, tx, xpub_path)
コード例 #5
0
    def _get_tx_info(self, tx: Transaction) -> TxInfo:
        value_delta = 0
        can_broadcast = False
        label = ''
        fee = height = conf = date_created = date_mined = None
        state = TxFlags.Unset

        wallet = self._wallet
        if tx.is_complete():
            metadata = wallet._transaction_cache.get_metadata(self._tx_hash)
            if metadata is None:
                # The transaction is not known to the wallet.
                status = _("External signed transaction")
                state = TxFlags.StateReceived | TxFlags.StateSigned
                can_broadcast = wallet._network is not None
            else:
                date_created = metadata.date_added

                # It is possible the wallet has the transaction but it is not associated with
                # any accounts. We still need to factor that in.
                fee = metadata.fee

                if metadata.height is not None and metadata.height > 0:
                    chain = app_state.headers.longest_chain()
                    try:
                        header = app_state.headers.header_at_height(
                            chain, metadata.height)
                        date_mined = header.timestamp
                    except MissingHeader:
                        pass

                label = wallet.get_transaction_label(self._tx_hash)

                state = wallet._transaction_cache.get_flags(
                    self._tx_hash) & TxFlags.STATE_MASK
                if state & TxFlags.StateSettled:
                    height = metadata.height
                    conf = max(wallet.get_local_height() - height + 1, 0)
                    status = _("{:,d} confirmations (in block {:,d})").format(
                        conf, height)
                elif state & TxFlags.StateCleared:
                    if metadata.height > 0:
                        status = _('Not verified')
                    else:
                        status = _('Unconfirmed')
                elif state & TxFlags.StateReceived:
                    status = _("Received")
                    can_broadcast = wallet._network is not None
                elif state & TxFlags.StateDispatched:
                    status = _("Dispatched")
                elif state & TxFlags.StateSigned:
                    status = _("Signed")
                    can_broadcast = wallet._network is not None
                else:
                    status = _('Unknown')

            account_deltas = wallet.get_transaction_deltas(self._tx_hash)
            value_delta = sum(row.total for row in account_deltas)
            # # It is possible that the wallet does not have the transaction.
            # if delta_result.match_count == 0:
            #     pass
            # else:
            #     value_delta += delta_result.total
        else:
            state = TxFlags.StateReceived

            # For now all inputs must come from the same account.
            for input in tx.inputs:
                value_delta -= input.value
            for output in tx.outputs:
                # If we know what type of script it is, we sign it's spend (or co-sign it).
                # We are sending to ourselves or we are receiving change.
                if output.script_type != ScriptType.NONE:
                    value_delta += output.value

            s, r = tx.signature_count()
            status = _("Unsigned") if s == 0 else _(
                'Partially signed') + ' (%d/%d)' % (s, r)

        if value_delta < 0:
            if fee is not None:
                # We remove the fee as the user can work out the fee is part of the sum themselves.
                amount = value_delta + fee
            else:
                amount = value_delta
        elif value_delta > 0:
            amount = value_delta
        else:
            amount = None

        return TxInfo(self._tx_hash, state, status, label, can_broadcast,
                      amount, fee, height, conf, date_mined, date_created)
コード例 #6
0
    def sign_transaction(self, tx: Transaction, password: str) -> None:
        if tx.is_complete():
            return

        try:
            p2pkhTransaction = True
            inputhasharray = []
            hasharray = []
            pubkeyarray = []

            # Build hasharray from inputs
            for txin in tx.inputs:
                if txin.type() != ScriptType.P2PKH:
                    p2pkhTransaction = False

                for x_pubkey in txin.x_pubkeys:
                    if self.is_signature_candidate(x_pubkey):
                        key_derivation = x_pubkey.bip32_path()
                        assert len(key_derivation) == 2
                        inputPath = "%s/%d/%d" % (self.get_derivation(),
                                                  *key_derivation)
                        inputHash = tx.preimage_hash(txin)
                        hasharray_i = {
                            'hash': inputHash.hex(),
                            'keypath': inputPath
                        }
                        hasharray.append(hasharray_i)
                        inputhasharray.append(inputHash)
                        break
                else:
                    self.give_error("No matching x_key for sign_transaction"
                                    )  # should never happen

            # Build pubkeyarray from annotated change outputs.
            # The user is on their own if they have unannotated non-change self-outputs.
            for txout in tx.outputs:
                if txout.x_pubkeys:
                    for xpubkey in [
                            xpk for xpk in txout.x_pubkeys
                            if self.is_signature_candidate(xpk)
                    ]:
                        key_path_text = compose_chain_string(
                            xpubkey.derivation_path())[1:]
                        changePath = self.get_derivation(
                        ) + key_path_text  # "/1/0", no "m"
                        pubkeyarray.append({
                            'pubkey':
                            xpubkey.to_public_key().to_hex(),
                            'keypath':
                            changePath,
                        })

            # Special serialization of the unsigned transaction for
            # the mobile verification app.
            # At the moment, verification only works for p2pkh transactions.
            if p2pkhTransaction:

                class CustomTXSerialization(Transaction):
                    @classmethod
                    def input_script(self, txin, estimate_size=False):
                        type_ = txin.type()
                        if type_ == ScriptType.P2PKH:
                            return Transaction.get_preimage_script(txin)
                        if type_ == ScriptType.MULTISIG_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 RuntimeError(f'unsupported type {type_}')

                tx_dbb_serialized = CustomTXSerialization.from_hex(
                    tx.serialize()).serialize()
            else:
                # We only need this for the signing echo / verification.
                tx_dbb_serialized = None

            # Build sign command
            dbb_signatures: List[Dict[str, Any]] = []
            steps = math.ceil(1.0 * len(hasharray) / self.maxInputs)
            for step in range(int(steps)):
                hashes = hasharray[step * self.maxInputs:(step + 1) *
                                   self.maxInputs]

                msg_data: Dict[str, Any] = {
                    "sign": {
                        "data": hashes,
                        "checkpub": pubkeyarray,
                    },
                }
                if tx_dbb_serialized is not None:
                    msg_data["sign"]["meta"] = sha256d(tx_dbb_serialized).hex()
                msg = json.dumps(msg_data).encode('ascii')
                assert self.plugin is not None
                dbb_client: DigitalBitbox_Client = self.plugin.get_client(self)

                if not dbb_client.is_paired():
                    raise Exception("Could not sign transaction.")

                reply = dbb_client.hid_send_encrypt(msg)
                if 'error' in reply:
                    raise Exception(reply['error']['message'])

                if 'echo' not in reply:
                    raise Exception("Could not sign transaction.")

                if self.plugin.is_mobile_paired(
                ) and tx_dbb_serialized is not None:
                    reply['tx'] = tx_dbb_serialized
                    self.plugin.comserver_post_notification(reply)

                if steps > 1:
                    self.handler.show_message(
                        _("Signing large transaction. Please be patient ...") +
                        "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for "
                          "3 seconds.") + " " +
                        _("(Touch {} of {})").format((step + 1), steps) +
                        "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ) + "\n\n")
                else:
                    self.handler.show_message(
                        _("Signing transaction...") + "\n\n" +
                        _("To continue, touch the Digital Bitbox's blinking light for "
                          "3 seconds.") + "\n\n" +
                        _("To cancel, briefly touch the blinking light or wait for the timeout."
                          ))

                # Send twice, first returns an echo for smart verification
                reply = dbb_client.hid_send_encrypt(msg)
                self.handler.finished()

                if 'error' in reply:
                    if reply["error"].get('code') in (600, 601):
                        # aborted via LED short touch or timeout
                        raise UserCancelled()
                    raise Exception(reply['error']['message'])

                if 'sign' not in reply:
                    raise Exception("Could not sign transaction.")

                dbb_signatures.extend(reply['sign'])

            # Fill signatures
            if len(dbb_signatures) != len(tx.inputs):
                raise RuntimeError("Incorrect number of transactions signed")
            for txin, siginfo, pre_hash in zip(tx.inputs, dbb_signatures,
                                               inputhasharray):
                if txin.is_complete():
                    continue
                for pubkey_index, x_pubkey in enumerate(txin.x_pubkeys):
                    compact_sig = bytes.fromhex(siginfo['sig'])
                    if 'recid' in siginfo:
                        # firmware > v2.1.1
                        recid = int(siginfo['recid'], 16)
                        recoverable_sig = compact_sig + bytes([recid])
                        pk = PublicKey.from_recoverable_signature(
                            recoverable_sig, pre_hash, None)
                    elif 'pubkey' in siginfo:
                        # firmware <= v2.1.1
                        pk = PublicKey.from_hex(siginfo['pubkey'])
                    if pk != x_pubkey.to_public_key():
                        continue
                    full_sig = (compact_signature_to_der(compact_sig) +
                                bytes([Transaction.nHashType() & 255]))
                    txin.signatures[pubkey_index] = full_sig
        except UserCancelled:
            raise
        except Exception as e:
            self.give_error(e, True)
        else:
            logger.debug("Transaction is_complete %s", tx.is_complete())
コード例 #7
0
ファイル: ledger.py プロジェクト: wagnehc/electrumsv
    def sign_transaction(self, tx: Transaction, password: str,
                         tx_context: TransactionContext) -> None:
        if tx.is_complete():
            return

        assert self.handler is not None
        client = self.get_client()
        inputs: List[YInput] = []
        inputsPaths = []
        chipInputs = []
        redeemScripts = []
        signatures = []
        changePath = ""
        changeAmount = None
        output = None
        outputAmount = None
        pin = ""
        self.get_client(
        )  # prompt for the PIN before displaying the dialog if necessary

        # Fetch inputs of the transaction to sign
        foundP2SHSpend = False
        allSpendsAreP2SH = True
        for txin in tx.inputs:
            foundP2SHSpend = foundP2SHSpend or txin.type(
            ) == ScriptType.MULTISIG_P2SH
            allSpendsAreP2SH = allSpendsAreP2SH and txin.type(
            ) == ScriptType.MULTISIG_P2SH

            for i, x_pubkey in enumerate(txin.x_pubkeys):
                if self.is_signature_candidate(x_pubkey):
                    txin_xpub_idx = i
                    inputPath = "%s/%d/%d" % (self.get_derivation()[2:],
                                              *x_pubkey.bip32_path())
                    break
            else:
                self.give_error("No matching x_key for sign_transaction"
                                )  # should never happen

            inputs.append(
                YInput(txin.value, Transaction.get_preimage_script_bytes(txin),
                       txin_xpub_idx, txin.sequence))
            inputsPaths.append(inputPath)

        # Sanity check
        if foundP2SHSpend and not allSpendsAreP2SH:
            self.give_error(
                "P2SH / regular input mixed in same transaction not supported")

        # Concatenate all the tx outputs as binary
        txOutput = pack_list(tx.outputs, XTxOutput.to_bytes)

        # Recognize outputs - only one output and one change is authorized
        if not foundP2SHSpend:
            keystore_fingerprint = self.get_fingerprint()
            assert tx.output_info is not None
            for tx_output, output_metadatas in zip(tx.outputs, tx.output_info):
                info = output_metadatas.get(keystore_fingerprint)
                if (info is not None) and len(tx.outputs) != 1:
                    key_derivation, xpubs, m = info
                    key_subpath = compose_chain_string(key_derivation)[1:]
                    changePath = self.get_derivation()[2:] + key_subpath
                    changeAmount = tx_output.value
                else:
                    output = classify_tx_output(tx_output)
                    outputAmount = tx_output.value

        self.handler.show_message(
            _("Confirm Transaction on your Ledger device..."))
        try:
            for i, utxo in enumerate(inputs):
                txin = tx.inputs[i]
                sequence = int_to_hex(utxo.sequence, 4)
                prevout_bytes = txin.prevout_bytes()
                value_bytes = prevout_bytes + pack_le_int64(utxo.value)
                chipInputs.append({
                    'value': value_bytes,
                    'witness': True,
                    'sequence': sequence
                })
                redeemScripts.append(utxo.script_sig)

            # Sign all inputs
            inputIndex = 0
            rawTx = tx.serialize()
            self.get_client().enableAlternate2fa(False)
            self.get_client().startUntrustedTransaction(
                True, inputIndex, chipInputs, redeemScripts[inputIndex])
            outputData = self.get_client().finalizeInputFull(txOutput)
            outputData['outputData'] = txOutput
            transactionOutput = outputData['outputData']
            if outputData['confirmationNeeded']:
                outputData['address'] = cast(ScriptTemplate,
                                             output).to_string()
                self.handler.finished()
                # the authenticate dialog and returns pin
                auth_pin = self.handler.get_auth(self, outputData)
                if not auth_pin:
                    raise UserWarning()
                pin = auth_pin
                self.handler.show_message(
                    _("Confirmed. Signing Transaction..."))
            while inputIndex < len(inputs):
                singleInput = [chipInputs[inputIndex]]
                self.get_client().startUntrustedTransaction(
                    False, 0, singleInput, redeemScripts[inputIndex])
                inputSignature = self.get_client().untrustedHashSign(
                    inputsPaths[inputIndex],
                    pin,
                    lockTime=tx.locktime,
                    sighashType=tx.nHashType())
                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 == 0x6985:  # cancelled by user
                return
            else:
                logger.exception("")
                self.give_error(e, True)
        except Exception as e:
            logger.exception("")
            self.give_error(e, True)
        finally:
            self.handler.finished()

        for txin, input, signature in zip(tx.inputs, inputs, signatures):
            txin.signatures[input.txin_xpub_idx] = signature
コード例 #8
0
    def get_tx_info(self, tx: Transaction) -> TxInfo:
        value_delta = 0
        can_broadcast = False
        label = ''
        fee = height = conf = timestamp = None
        tx_hash = tx.hash()
        if tx.is_complete():
            entry = self._wallet._transaction_cache.get_cached_entry(tx_hash)
            metadata = entry.metadata
            fee = metadata.fee
            label = self._account.get_transaction_label(tx_hash)
            value_delta = self._account.get_transaction_delta(tx_hash)
            if value_delta is None:
                # When the transaction is fully signed and updated before the delta changes
                # are committed to the database (pending write).
                value_delta = 0
            if self._account.has_received_transaction(tx_hash):
                if (entry.flags & TxFlags.StateSettled
                        or entry.flags & TxFlags.StateCleared
                        and metadata.height > 0):
                    chain = app_state.headers.longest_chain()
                    try:
                        header = app_state.headers.header_at_height(
                            chain, metadata.height)
                        timestamp = header.timestamp
                    except MissingHeader:
                        pass

                    if entry.flags & TxFlags.StateSettled:
                        height = metadata.height
                        conf = max(
                            self._wallet.get_local_height() - height + 1, 0)
                        status = _(
                            "{:,d} confirmations (in block {:,d})").format(
                                conf, height)
                    else:
                        status = _('Not verified')
                else:
                    status = _('Unconfirmed')
            else:
                status = _("Signed")
                can_broadcast = self._wallet._network is not None
        else:
            for input in tx.inputs:
                value_delta -= input.value
            for output in tx.outputs:
                # If we know what type of script it is, we sign it's spend (or co-sign it).
                if output.script_type != ScriptType.NONE:
                    value_delta += output.value

            s, r = tx.signature_count()
            status = _("Unsigned") if s == 0 else _(
                'Partially signed') + ' (%d/%d)' % (s, r)

        if value_delta < 0:
            if fee is not None:
                amount = value_delta + fee
            else:
                amount = value_delta
        elif value_delta > 0:
            amount = value_delta
        else:
            amount = None

        return TxInfo(tx_hash, status, label, can_broadcast, amount, fee,
                      height, conf, timestamp)
コード例 #9
0
ファイル: cosigner_pool.py プロジェクト: skywills/electrumsv
 def show_button(self, wallet: Abstract_Wallet, tx: Transaction) -> bool:
     if tx.is_complete() or wallet.can_sign(tx):
         return False
     return any(self.is_theirs(wallet, item, tx) for item in self.items)