Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
def is_any_tx_output_on_change_branch(tx: PartialTransaction) -> bool:
    return any([txout.is_change for txout in tx.outputs()])
Beispiel #4
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.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)