예제 #1
0
 def fill_psbt(self, b64psbt, non_witness: bool = True, xpubs: bool = True):
     psbt = PSBT()
     psbt.deserialize(b64psbt)
     if non_witness:
         for i, inp in enumerate(psbt.tx.vin):
             txid = inp.prevout.hash.to_bytes(32, 'big').hex()
             try:
                 res = self.cli.gettransaction(txid)
             except:
                 raise SpecterError(
                     "Can't find previous transaction in the wallet.")
             stream = BytesIO(bytes.fromhex(res["hex"]))
             prevtx = CTransaction()
             prevtx.deserialize(stream)
             psbt.inputs[i].non_witness_utxo = prevtx
     if xpubs:
         # for multisig add xpub fields
         if len(self.keys) > 1:
             for k in self.keys:
                 key = b'\x01' + decode_base58(k.xpub)
                 if k.fingerprint != '':
                     fingerprint = bytes.fromhex(k.fingerprint)
                 else:
                     fingerprint = get_xpub_fingerprint(k.xpub)
                 if k.derivation != '':
                     der = der_to_bytes(k.derivation)
                 else:
                     der = b''
                 value = fingerprint + der
                 psbt.unknown[key] = value
     return psbt.serialize()
예제 #2
0
 def fill_psbt(self, b64psbt):
     psbt = PSBT()
     psbt.deserialize(b64psbt)
     for i, inp in enumerate(psbt.tx.vin):
         txid = inp.prevout.hash.to_bytes(32,'big').hex()
         try:
             res = self.cli.gettransaction(txid)
         except:
             raise SpecterError("Can't find previous transaction in the wallet.")
         stream = BytesIO(bytes.fromhex(res["hex"]))
         prevtx = CTransaction()
         prevtx.deserialize(stream)
         psbt.inputs[i].non_witness_utxo = prevtx
     return psbt.serialize()
예제 #3
0
def get_txid(tx):
    b = BytesIO(bytes.fromhex(tx))
    t = CTransaction()
    t.deserialize(b)
    for inp in t.vin:
        inp.scriptSig = b""
    t.rehash()
    return t.hash
예제 #4
0
    def fill_psbt(self, b64psbt, non_witness: bool = True, xpubs: bool = True):
        psbt = PSBT()
        psbt.deserialize(b64psbt)
        if non_witness:
            for i, inp in enumerate(psbt.tx.vin):
                txid = inp.prevout.hash.to_bytes(32, "big").hex()
                try:
                    res = self.gettransaction(txid)
                    stream = BytesIO(bytes.fromhex(res["hex"]))
                    prevtx = CTransaction()
                    prevtx.deserialize(stream)
                    psbt.inputs[i].non_witness_utxo = prevtx
                except:
                    logger.error(
                        "Can't find previous transaction in the wallet. Signing might not be possible for certain devices..."
                    )
        else:
            # remove non_witness_utxo if we don't want them
            for inp in psbt.inputs:
                if inp.witness_utxo is not None:
                    inp.non_witness_utxo = None

        if xpubs:
            # for multisig add xpub fields
            if len(self.keys) > 1:
                for k in self.keys:
                    key = b"\x01" + decode_base58(k.xpub)
                    if k.fingerprint != "":
                        fingerprint = bytes.fromhex(k.fingerprint)
                    else:
                        fingerprint = get_xpub_fingerprint(k.xpub)
                    if k.derivation != "":
                        der = der_to_bytes(k.derivation)
                    else:
                        der = b""
                    value = fingerprint + der
                    psbt.unknown[key] = value
        return psbt.serialize()
예제 #5
0
    def sign_tx(self, tx):
        c_tx = CTransaction(tx.tx)
        tx_bytes = c_tx.serialize_with_witness()

        # Master key fingerprint
        master_fpr = hash160(
            compress_public_key(
                self.app.getWalletPublicKey("")["publicKey"]))[:4]
        # An entry per input, each with 0 to many keys to sign with
        all_signature_attempts = [[]] * len(c_tx.vin)

        # Get the app version to determine whether to use Trusted Input for segwit
        version = self.app.getFirmwareVersion()
        use_trusted_segwit = (
            version["major_version"] == 1
            and version["minor_version"] >= 4) or version["major_version"] > 1

        # NOTE: We only support signing Segwit inputs, where we can skip over non-segwit
        # inputs, or non-segwit inputs, where *all* inputs are non-segwit. This is due
        # to Ledger's mutually exclusive signing steps for each type.
        segwit_inputs = []
        # Legacy style inputs
        legacy_inputs = []

        has_segwit = False
        has_legacy = False

        script_codes = [[]] * len(c_tx.vin)

        # Detect changepath, (p2sh-)p2(w)pkh only
        change_path = ""
        for txout, i_num in zip(c_tx.vout, range(len(c_tx.vout))):
            # Find which wallet key could be change based on hdsplit: m/.../1/k
            # Wallets shouldn't be sending to change address as user action
            # otherwise this will get confused
            for pubkey, path in tx.outputs[i_num].hd_keypaths.items():
                if (struct.pack("<I", path[0]) == master_fpr and len(path) > 2
                        and path[-2] == 1):
                    # For possible matches, check if pubkey matches possible template
                    if (hash160(pubkey) in txout.scriptPubKey or hash160(
                            bytearray.fromhex("0014") + hash160(pubkey))
                            in txout.scriptPubKey):
                        change_path = ""
                        for index in path[1:]:
                            change_path += str(index) + "/"
                        change_path = change_path[:-1]

        for txin, psbt_in, i_num in zip(c_tx.vin, tx.inputs,
                                        range(len(c_tx.vin))):

            seq = format(txin.nSequence, "x")
            seq = seq.zfill(8)
            seq = bytearray.fromhex(seq)
            seq.reverse()
            seq_hex = "".join("{:02x}".format(x) for x in seq)

            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(i_num))
                utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n]
            if utxo is None:
                raise Exception(
                    "PSBT is missing input utxo information, cannot sign")
            scriptcode = utxo.scriptPubKey

            if is_p2sh(scriptcode):
                if len(psbt_in.redeem_script) == 0:
                    continue
                scriptcode = psbt_in.redeem_script

            is_wit, _, _ = is_witness(scriptcode)

            segwit_inputs.append({
                "value":
                txin.prevout.serialize() + struct.pack("<Q", utxo.nValue),
                "witness":
                True,
                "sequence":
                seq_hex,
            })
            if is_wit:
                if is_p2wsh(scriptcode):
                    if len(psbt_in.witness_script) == 0:
                        continue
                    scriptcode = psbt_in.witness_script
                elif is_p2wpkh(scriptcode):
                    _, _, wit_prog = is_witness(scriptcode)
                    scriptcode = b"\x76\xa9\x14" + wit_prog + b"\x88\xac"
                else:
                    continue
                has_segwit = True
            else:
                # We only need legacy inputs in the case where all inputs are legacy, we check
                # later
                ledger_prevtx = bitcoinTransaction(
                    psbt_in.non_witness_utxo.serialize())
                legacy_inputs.append(
                    self.app.getTrustedInput(ledger_prevtx, txin.prevout.n))
                legacy_inputs[-1]["sequence"] = seq_hex
                has_legacy = True

            if psbt_in.non_witness_utxo and use_trusted_segwit:
                ledger_prevtx = bitcoinTransaction(
                    psbt_in.non_witness_utxo.serialize())
                segwit_inputs[-1].update(
                    self.app.getTrustedInput(ledger_prevtx, txin.prevout.n))

            pubkeys = []
            signature_attempts = []

            # Save scriptcode for later signing
            script_codes[i_num] = scriptcode

            # Find which pubkeys could sign this input (should be all?)
            for pubkey in psbt_in.hd_keypaths.keys():
                if hash160(pubkey) in scriptcode or pubkey in scriptcode:
                    pubkeys.append(pubkey)

            # Figure out which keys in inputs are from our wallet
            for pubkey in pubkeys:
                keypath = psbt_in.hd_keypaths[pubkey]
                if master_fpr == struct.pack("<I", keypath[0]):
                    # Add the keypath strings
                    keypath_str = ""
                    for index in keypath[1:]:
                        keypath_str += str(index) + "/"
                    keypath_str = keypath_str[:-1]
                    signature_attempts.append([keypath_str, pubkey])

            all_signature_attempts[i_num] = signature_attempts

        # Sign any segwit inputs
        if has_segwit:
            # Process them up front with all scriptcodes blank
            blank_script_code = bytearray()
            for i in range(len(segwit_inputs)):
                self.app.startUntrustedTransaction(
                    i == 0,
                    i,
                    segwit_inputs,
                    script_codes[i]
                    if use_trusted_segwit else blank_script_code,
                    c_tx.nVersion,
                )

            # Number of unused fields for Nano S, only changepath and transaction in bytes req
            self.app.finalizeInput(b"DUMMY", -1, -1, change_path, tx_bytes)

            # For each input we control do segwit signature
            for i in range(len(segwit_inputs)):
                for signature_attempt in all_signature_attempts[i]:
                    self.app.startUntrustedTransaction(False, 0,
                                                       [segwit_inputs[i]],
                                                       script_codes[i],
                                                       c_tx.nVersion)
                    tx.inputs[i].partial_sigs[
                        signature_attempt[1]] = self.app.untrustedHashSign(
                            signature_attempt[0], "", c_tx.nLockTime, 0x01)
        elif has_legacy:
            first_input = True
            # Legacy signing if all inputs are legacy
            for i in range(len(legacy_inputs)):
                for signature_attempt in all_signature_attempts[i]:
                    assert tx.inputs[i].non_witness_utxo is not None
                    self.app.startUntrustedTransaction(first_input, i,
                                                       legacy_inputs,
                                                       script_codes[i],
                                                       c_tx.nVersion)
                    self.app.finalizeInput(b"DUMMY", -1, -1, change_path,
                                           tx_bytes)
                    tx.inputs[i].partial_sigs[
                        signature_attempt[1]] = self.app.untrustedHashSign(
                            signature_attempt[0], "", c_tx.nLockTime, 0x01)
                    first_input = False

        # Send PSBT back
        return {"psbt": tx.serialize()}