예제 #1
0
 def test_valid_address(self):
     """Test whether valid addresses decode to the correct output."""
     for (address, hexscript) in VALID_ADDRESS:
         hrp = "bc"
         witver, witprog = segwit_addr.decode(hrp, address)
         if witver is None:
             hrp = "tb"
             witver, witprog = segwit_addr.decode(hrp, address)
         self.assertIsNotNone(witver, address)
         scriptpubkey = segwit_scriptpubkey(witver, witprog)
         self.assertEqual(scriptpubkey, binascii.unhexlify(hexscript))
         addr = segwit_addr.encode(hrp, witver, witprog)
         self.assertEqual(address.lower(), addr)
예제 #2
0
    def sign_tx(self, tx: PSBT) -> PSBT:
        """
        Sign a transaction with the Trezor. There are some limitations to what transactions can be signed.

        - Multisig inputs are limited to at most n-of-15 multisigs. This is a firmware limitation.
        - Transactions with arbitrary input scripts (scriptPubKey, redeemScript, or witnessScript) and arbitrary output scripts cannot be signed. This is a firmware limitation.
        - Send-to-self transactions will result in no prompt for outputs as all outputs will be detected as change.
        """
        self._check_unlocked()

        # Get this devices master key fingerprint
        master_key = btc.get_public_node(self.client, [0x80000000], coin_name="Bitcoin")
        master_fp = get_xpub_fingerprint(master_key.xpub)

        # Do multiple passes for multisig
        passes = 1
        p = 0

        while p < passes:
            # Prepare inputs
            inputs = []
            to_ignore = (
                []
            )  # Note down which inputs whose signatures we're going to ignore
            for input_num, (psbt_in, txin) in py_enumerate(
                list(zip(tx.inputs, tx.tx.vin))
            ):
                txinputtype = messages.TxInputType(
                    prev_hash=ser_uint256(txin.prevout.hash)[::-1],
                    prev_index=txin.prevout.n,
                    sequence=txin.nSequence,
                )

                # Detrermine spend type
                scriptcode = b""
                utxo = None
                if psbt_in.witness_utxo:
                    utxo = psbt_in.witness_utxo
                if psbt_in.non_witness_utxo:
                    if txin.prevout.hash != psbt_in.non_witness_utxo.sha256:
                        raise BadArgumentError(
                            "Input {} has a non_witness_utxo with the wrong hash".format(
                                input_num
                            )
                        )
                    utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n]
                if utxo is None:
                    continue
                scriptcode = utxo.scriptPubKey

                # Check if P2SH
                p2sh = False
                if is_p2sh(scriptcode):
                    # Look up redeemscript
                    if len(psbt_in.redeem_script) == 0:
                        continue
                    scriptcode = psbt_in.redeem_script
                    p2sh = True

                # Check segwit
                is_wit, _, _ = is_witness(scriptcode)

                if is_wit:
                    if p2sh:
                        txinputtype.script_type = (
                            messages.InputScriptType.SPENDP2SHWITNESS
                        )
                    else:
                        txinputtype.script_type = messages.InputScriptType.SPENDWITNESS
                else:
                    txinputtype.script_type = messages.InputScriptType.SPENDADDRESS
                txinputtype.amount = utxo.nValue

                # Check if P2WSH
                p2wsh = False
                if is_p2wsh(scriptcode):
                    # Look up witnessscript
                    if len(psbt_in.witness_script) == 0:
                        continue
                    scriptcode = psbt_in.witness_script
                    p2wsh = True

                def ignore_input() -> None:
                    txinputtype.address_n = [
                        0x80000000 | 84,
                        0x80000000 | (0 if self.chain == Chain.MAIN else 1),
                        0x80000000,
                        0,
                        0,
                    ]
                    txinputtype.multisig = None
                    txinputtype.script_type = messages.InputScriptType.SPENDWITNESS
                    inputs.append(txinputtype)
                    to_ignore.append(input_num)

                # Check for multisig
                is_ms, multisig = parse_multisig(scriptcode, tx.xpub, psbt_in)
                if is_ms:
                    # Add to txinputtype
                    txinputtype.multisig = multisig
                    if not is_wit:
                        if utxo.is_p2sh:
                            txinputtype.script_type = (
                                messages.InputScriptType.SPENDMULTISIG
                            )
                        else:
                            # Cannot sign bare multisig, ignore it
                            ignore_input()
                            continue
                elif not is_ms and not is_wit and not is_p2pkh(scriptcode):
                    # Cannot sign unknown spk, ignore it
                    ignore_input()
                    continue
                elif not is_ms and is_wit and p2wsh:
                    # Cannot sign unknown witness script, ignore it
                    ignore_input()
                    continue

                # Find key to sign with
                found = False  # Whether we have found a key to sign with
                found_in_sigs = (
                    False  # Whether we have found one of our keys in the signatures
                )
                our_keys = 0
                for key in psbt_in.hd_keypaths.keys():
                    keypath = psbt_in.hd_keypaths[key]
                    if keypath.fingerprint == master_fp:
                        if (
                            key in psbt_in.partial_sigs
                        ):  # This key already has a signature
                            found_in_sigs = True
                            continue
                        if (
                            not found
                        ):  # This key does not have a signature and we don't have a key to sign with yet
                            txinputtype.address_n = keypath.path
                            found = True
                        our_keys += 1

                # Determine if we need to do more passes to sign everything
                if our_keys > passes:
                    passes = our_keys

                if (
                    not found and not found_in_sigs
                ):  # None of our keys were in hd_keypaths or in partial_sigs
                    # This input is not one of ours
                    ignore_input()
                    continue
                elif (
                    not found and found_in_sigs
                ):  # All of our keys are in partial_sigs, ignore whatever signature is produced for this input
                    ignore_input()
                    continue

                # append to inputs
                inputs.append(txinputtype)

            # address version byte
            if self.chain != Chain.MAIN:
                p2pkh_version = b"\x6f"
                p2sh_version = b"\xc4"
                bech32_hrp = "tb"
            else:
                p2pkh_version = b"\x00"
                p2sh_version = b"\x05"
                bech32_hrp = "bc"

            # prepare outputs
            outputs = []
            for i, out in py_enumerate(tx.tx.vout):
                txoutput = messages.TxOutputType(amount=out.nValue)
                txoutput.script_type = messages.OutputScriptType.PAYTOADDRESS
                if out.is_p2pkh():
                    txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version)
                elif out.is_p2sh():
                    txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version)
                elif out.is_opreturn():
                    txoutput.script_type = messages.OutputScriptType.PAYTOOPRETURN
                    txoutput.op_return_data = out.scriptPubKey[2:]
                else:
                    wit, ver, prog = out.is_witness()
                    if wit:
                        txoutput.address = bech32.encode(bech32_hrp, ver, prog)
                    else:
                        raise BadArgumentError("Output is not an address")

                # Add the derivation path for change
                psbt_out = tx.outputs[i]
                for _, keypath in psbt_out.hd_keypaths.items():
                    if keypath.fingerprint != master_fp:
                        continue
                    wit, ver, prog = out.is_witness()
                    if out.is_p2pkh():
                        txoutput.address_n = keypath.path
                        txoutput.address = None
                    elif wit:
                        txoutput.script_type = messages.OutputScriptType.PAYTOWITNESS
                        txoutput.address_n = keypath.path
                        txoutput.address = None
                    elif out.is_p2sh() and psbt_out.redeem_script:
                        wit, ver, prog = CTxOut(0, psbt_out.redeem_script).is_witness()
                        if wit and len(prog) in [20, 32]:
                            txoutput.script_type = (
                                messages.OutputScriptType.PAYTOP2SHWITNESS
                            )
                            txoutput.address_n = keypath.path
                            txoutput.address = None

                # add multisig info
                is_ms, multisig = parse_multisig(
                    psbt_out.witness_script or psbt_out.redeem_script, tx.xpub, psbt_out
                )
                if is_ms:
                    txoutput.multisig = multisig

                # append to outputs
                outputs.append(txoutput)

            # Prepare prev txs
            prevtxs = {}
            for psbt_in in tx.inputs:
                if psbt_in.non_witness_utxo:
                    prev = psbt_in.non_witness_utxo

                    t = messages.TransactionType()
                    t.version = prev.nVersion
                    t.lock_time = prev.nLockTime

                    for vin in prev.vin:
                        i = messages.TxInputType(
                            prev_hash=ser_uint256(vin.prevout.hash)[::-1],
                            prev_index=vin.prevout.n,
                            script_sig=vin.scriptSig,
                            sequence=vin.nSequence,
                        )
                        t.inputs.append(i)

                    for vout in prev.vout:
                        o = messages.TxOutputBinType(
                            amount=vout.nValue,
                            script_pubkey=vout.scriptPubKey,
                        )
                        t.bin_outputs.append(o)
                    logging.debug(psbt_in.non_witness_utxo.hash)
                    assert psbt_in.non_witness_utxo.sha256 is not None
                    prevtxs[ser_uint256(psbt_in.non_witness_utxo.sha256)[::-1]] = t

            # Sign the transaction
            signed_tx = btc.sign_tx(
                client=self.client,
                coin_name=self.coin_name,
                inputs=inputs,
                outputs=outputs,
                prev_txes=prevtxs,
                version=tx.tx.nVersion,
                lock_time=tx.tx.nLockTime,
            )

            # Each input has one signature
            for input_num, (psbt_in, sig) in py_enumerate(
                list(zip(tx.inputs, signed_tx[0]))
            ):
                if input_num in to_ignore:
                    continue
                for pubkey in psbt_in.hd_keypaths.keys():
                    fp = psbt_in.hd_keypaths[pubkey].fingerprint
                    if fp == master_fp and pubkey not in psbt_in.partial_sigs:
                        psbt_in.partial_sigs[pubkey] = sig + b"\x01"
                        break

            p += 1

        return tx
예제 #3
0
 def test_invalid_address_enc(self):
     """Test whether address encoding fails on invalid input."""
     for hrp, version, length in INVALID_ADDRESS_ENC:
         code = segwit_addr.encode(hrp, version, [0] * length)
         self.assertIsNone(code)