Пример #1
0
    def combinepsbt(self, psbts, *args, **kwargs):
        if len(psbts) == 0:
            raise RpcError("Provide at least one psbt")
        tx = PSET.from_string(psbts[0])
        for b64 in psbts[1:]:
            t2 = PSET.from_string(b64)
            tx.version = tx.version or t2.version
            tx.tx_version = tx.tx_version or t2.tx_version
            tx.locktime = tx.locktime or t2.locktime
            tx.xpubs.update(t2.xpubs)
            tx.unknown.update(t2.unknown)

            for inp1, inp2 in zip(tx.inputs, t2.inputs):
                inp1.value = inp1.value or inp2.value
                inp1.value_blinding_factor = (
                    inp1.value_blinding_factor or inp2.value_blinding_factor
                )
                inp1.asset = inp1.asset or inp2.asset
                inp1.asset_blinding_factor = (
                    inp1.asset_blinding_factor or inp2.asset_blinding_factor
                )
                inp1.txid = inp1.txid or inp2.txid
                inp1.vout = inp1.vout or inp2.vout
                inp1.sequence = inp1.sequence or inp2.sequence
                inp1.non_witness_utxo = inp1.non_witness_utxo or inp2.non_witness_utxo
                inp1.sighash_type = inp1.sighash_type or inp2.sighash_type
                inp1.redeem_script = inp1.redeem_script or inp2.redeem_script
                inp1.witness_script = inp1.witness_script or inp2.witness_script
                inp1.final_scriptsig = inp1.final_scriptsig or inp2.final_scriptsig
                inp1.final_scriptwitness = (
                    inp1.final_scriptwitness or inp2.final_scriptwitness
                )
                inp1.partial_sigs.update(inp2.partial_sigs)
                inp1.bip32_derivations.update(inp2.bip32_derivations)
                inp1.unknown.update(inp2.unknown)
                inp1.range_proof = inp1.range_proof or inp2.range_proof

            for out1, out2 in zip(tx.outputs, t2.outputs):
                out1.value_commitment = out1.value_commitment or out2.value_commitment
                out1.value_blinding_factor = (
                    out1.value_blinding_factor or out2.value_blinding_factor
                )
                out1.asset_commitment = out1.asset_commitment or out2.asset_commitment
                out1.asset_blinding_factor = (
                    out1.asset_blinding_factor or out2.asset_blinding_factor
                )
                out1.range_proof = out1.range_proof or out2.range_proof
                out1.surjection_proof = out1.surjection_proof or out2.surjection_proof
                out1.ecdh_pubkey = out1.ecdh_pubkey or out2.ecdh_pubkey
                out1.blinding_pubkey = out1.blinding_pubkey or out2.blinding_pubkey
                out1.asset = out1.asset or out2.asset

                out1.value = out1.value or out2.value
                out1.script_pubkey = out1.script_pubkey or out2.script_pubkey
                out1.unknown = out1.unknown or out2.unknown
                out1.redeem_script = out1.redeem_script or out2.redeem_script
                out1.witness_script = out1.witness_script or out2.witness_script
                out1.bip32_derivations.update(out2.bip32_derivations)
                out1.unknown.update(out2.unknown)
        return str(tx)
Пример #2
0
    def decodepsbt(self, b64psbt, *args, **kwargs):
        tx = PSET.from_string(b64psbt)
        inputs = [(inp.value, inp.asset) for inp in tx.inputs]
        for inp in tx.inputs:
            inp.value = None
            inp.asset = None
            inp.value_blinding_factor = None
            inp.asset_blinding_factor = None

        for out in tx.outputs:
            if out.asset and out.value:
                # out.asset = None
                out.asset_blinding_factor = None
                # out.value = None
                out.value_blinding_factor = None
                out.asset_commitment = None
                out.value_commitment = None
                out.range_proof = None
                out.surjection_proof = None
                out.ecdh_pubkey = None
        b64psbt = str(tx)

        decoded = super().__getattr__("decodepsbt")(b64psbt, *args, **kwargs)
        # pset branch - no fee and global tx fields...
        if "tx" not in decoded or "fee" not in decoded:
            pset = PSET.from_string(b64psbt)
            if "tx" not in decoded:
                decoded["tx"] = self.decoderawtransaction(str(pset.tx))
            if "fee" not in decoded:
                decoded["fee"] = pset.fee() * 1e-8
        for out in decoded["outputs"]:
            if "value" not in out:
                out["value"] = -1
        for out in decoded["tx"]["vout"]:
            if "value" not in out:
                out["value"] = -1
        for i, (v, a) in enumerate(inputs):
            inp = decoded["tx"]["vin"][i]  # old psbt
            inp2 = decoded["inputs"][i]  # new psbt
            if "utxo_rangeproof" in inp2:
                inp2.pop("utxo_rangeproof")
            a = bytes(reversed(a[-32:])).hex()
            v = round(v * 1e-8, 8)
            if "value" not in inp:
                inp["value"] = v
            if "asset" not in inp:
                inp["asset"] = a
            if "value" not in inp2:
                inp2["value"] = v
            if "asset" not in inp2:
                inp2["asset"] = a
        return decoded
Пример #3
0
 def test_pset(self):
     psets = [
         "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",
     ]
     for b64 in psets:
         tx = PSET.from_string(b64)
         self.assertTrue(str(tx), b64)
Пример #4
0
    def create_psbts(self, base64_psbt, wallet):
        try:
            # remove rangeproofs and add sighash alls
            psbt = PSET.from_string(base64_psbt)
            for out in psbt.outputs:
                out.range_proof = None
                out.surjection_proof = None
            for inp in psbt.inputs:
                if not inp.sighash_type:
                    inp.sighash_type = LSIGHASH.ALL
            base64_psbt = psbt.to_string()
        except:
            pass
        psbts = super().create_psbts(base64_psbt, wallet)
        # remove non-witness utxo if they are there to reduce QR code size
        updated_psbt = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=False)
        try:
            qr_psbt = PSBT.from_string(updated_psbt)
        except:
            qr_psbt = PSET.from_string(updated_psbt)
        # find my key
        fgp = None
        derivation = None
        for k in wallet.keys:
            if k in self.keys and k.fingerprint and k.derivation:
                fgp = bytes.fromhex(k.fingerprint)
                derivation = bip32.parse_path(k.derivation)
                break
        # remove unnecessary derivations from inputs and outputs
        for inp in qr_psbt.inputs + qr_psbt.outputs:
            # keep only my derivation
            for k in list(inp.bip32_derivations.keys()):
                if fgp and inp.bip32_derivations[k].fingerprint != fgp:
                    inp.bip32_derivations.pop(k, None)
        # remove scripts from outputs (DIY should know about the wallet)
        for out in qr_psbt.outputs:
            out.witness_script = None
            out.redeem_script = None
        # remove partial sigs from inputs
        for inp in qr_psbt.inputs:
            inp.partial_sigs = {}
        psbts["qrcode"] = qr_psbt.to_string()

        # we can add xpubs to SD card, but non_witness can be too large for MCU
        psbts["sdcard"] = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True)
        return psbts
Пример #5
0
def clean_psbt(b64psbt):
    try:
        psbt = PSBT.from_string(b64psbt)
    except:
        psbt = PSET.from_string(b64psbt)
    for inp in psbt.inputs:
        if inp.witness_utxo is not None and inp.non_witness_utxo is not None:
            inp.non_witness_utxo = None
    return psbt.to_string()
Пример #6
0
 def finalizepsbt(self, psbt, *args, **kwargs):
     psbt = to_canonical_pset(psbt)
     res = super().__getattr__("finalizepsbt")(psbt, *args, **kwargs)
     if res["complete"] == False:
         try:
             # try using our finalizer
             tx = finalizer.finalize_psbt(PSET.from_string(psbt))
             if tx and self.testmempoolaccept([str(tx)]):
                 return {"complete": True, "hex": str(tx)}
         except Exception as e:
             logger.exception(e)
     return res
Пример #7
0
    def sign_pset(self, b64pset: str) -> str:
        """Signs specter-desktop specific Liquid PSET transaction"""
        mfp = self.get_master_fingerprint()
        pset = PSET.from_string(b64pset)
        commitments = self._blind(pset)
        ins = [
            {
                "is_witness": True,
                # "input_tx": inp.non_witness_utxo.serialize(),
                "script": inp.witness_script.data
                if inp.witness_script
                else script.p2pkh_from_p2wpkh(inp.script_pubkey).data,
                "value_commitment": write_commitment(inp.utxo.value),
                "path": [
                    der
                    for der in inp.bip32_derivations.values()
                    if der.fingerprint == mfp
                ][0].derivation,
            }
            for inp in pset.inputs
        ]
        change = [
            {
                "path": [
                    der
                    for pub, der in out.bip32_derivations.items()
                    if der.fingerprint == mfp
                ][0].derivation,
                "variant": self._get_script_type(out),
            }
            if out.bip32_derivations and self._get_script_type(out) is not None
            else None
            for out in pset.outputs
        ]
        rawtx = pset.blinded_tx.serialize()

        signatures = self.jade.sign_liquid_tx(
            self._network(), rawtx, ins, commitments, change
        )
        for i, inp in py_enumerate(pset.inputs):
            inp.partial_sigs[
                [
                    pub
                    for pub, der in inp.bip32_derivations.items()
                    if der.fingerprint == mfp
                ][0]
            ] = signatures[i]
        # we must finalize here because it has different commitments and only supports singlesig
        return str(finalize_psbt(pset))
Пример #8
0
    def _cleanpsbt(self, psbt):
        """Removes stuff that Core doesn't like"""
        tx = PSET.from_string(psbt)
        for inp in tx.inputs:
            inp.value = None
            inp.asset = None
            inp.value_blinding_factor = None
            inp.asset_blinding_factor = None

        for out in tx.outputs:
            if out.is_blinded:
                out.asset = None
                out.asset_blinding_factor = None
                out.value = None
                out.value_blinding_factor = None
        return str(tx)
Пример #9
0
 def decodepsbt(self, b64psbt, *args, **kwargs):
     decoded = super().__getattr__("decodepsbt")(b64psbt, *args, **kwargs)
     # pset branch - no fee and global tx fields...
     if "tx" not in decoded or "fee" not in decoded:
         pset = PSET.from_string(b64psbt)
         if "tx" not in decoded:
             decoded["tx"] = self.decoderawtransaction(str(pset.tx))
         if "fee" not in decoded:
             decoded["fee"] = pset.fee() * 1e-8
     for out in decoded["outputs"]:
         if "value" not in out:
             out["value"] = -1
     for out in decoded["tx"]["vout"]:
         if "value" not in out:
             out["value"] = -1
     return decoded
Пример #10
0
    def fill_psbt(self, b64psbt, non_witness: bool = True, xpubs: bool = True):
        psbt = PSET.from_string(b64psbt)

        if non_witness:
            for inp in psbt.inputs:
                # we don't need to fill what is already filled
                if inp.non_witness_utxo is not None:
                    continue
                txid = inp.txid.hex()
                try:
                    res = self.gettransaction(txid)
                    inp.non_witness_utxo = Transaction.from_string(res["hex"])
                except Exception as e:
                    logger.error(
                        f"Can't find previous transaction in the wallet. Signing might not be possible for certain devices... Txid: {txid}, Exception: {e}"
                    )
        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 = bip32.HDKey.from_string(k.xpub)
                    if k.fingerprint != "":
                        fingerprint = bytes.fromhex(k.fingerprint)
                    else:
                        fingerprint = get_xpub_fingerprint(k.xpub)
                    if k.derivation != "":
                        der = bip32.parse_path(k.derivation)
                    else:
                        der = []
                    psbt.xpub[key] = DerivationPath(fingerprint, der)
        else:
            psbt.xpub = {}
        return psbt.to_string()
Пример #11
0
def to_canonical_pset(pset: str) -> str:
    """
    Removes unblinded information from the transaction
    so Elements Core can decode it
    """
    # if we got psbt, not pset - just return
    if not pset.startswith("cHNl"):
        return pset
    tx = PSET.from_string(pset)

    for inp in tx.inputs:
        inp.value = None
        inp.asset = None
        inp.value_blinding_factor = None
        inp.asset_blinding_factor = None

    for out in tx.outputs:
        if out.is_blinded:
            out.asset = None
            out.asset_blinding_factor = None
            out.value = None
            out.value_blinding_factor = None
    return str(tx)
Пример #12
0
    def sign_with_descriptor(self, d1, d2, root, selfblind=False):
        rpc = daemon.rpc
        wname = random_wallet_name()
        # to derive addresses
        desc1 = Descriptor.from_string(d1)
        desc2 = Descriptor.from_string(d2)
        # recv addr 2
        addr1 = desc1.derive(2).address(net)
        # change addr 3
        addr2 = desc2.derive(3).address(net)

        # to add checksums
        d1 = add_checksum(str(d1))
        d2 = add_checksum(str(d2))
        rpc.createwallet(wname, True, True, "", False, True, False)
        w = daemon.wallet(wname)
        res = w.importdescriptors([{
            "desc": d1,
            "active": True,
            "internal": False,
            "timestamp": "now",
        }, {
            "desc": d2,
            "active": True,
            "internal": True,
            "timestamp": "now",
        }])
        self.assertTrue(all([k["success"] for k in res]))
        bpk = b"1" * 32
        w.importmasterblindingkey(bpk.hex())
        addr1 = w.getnewaddress()
        wdefault = daemon.wallet()
        wdefault.sendtoaddress(addr1, 0.1)
        daemon.mine()
        waddr = wdefault.getnewaddress()
        psbt = w.walletcreatefundedpsbt([], [{
            waddr: 0.002
        }], 0, {
            "includeWatching": True,
            "changeAddress": addr1,
            "fee_rate": 1
        }, True)
        unsigned = psbt["psbt"]

        # fix blinding change address
        tx = PSBT.from_string(unsigned)
        _, bpub = addr_decode(addr1)
        if not tx.outputs[psbt["changepos"]].blinding_pubkey:
            tx.outputs[psbt["changepos"]].blinding_pubkey = bpub.sec()
            unsigned = str(tx)

        # blind with custom message
        if selfblind:
            unblinded_psbt = PSBT.from_string(unsigned)
            # generate all blinding stuff
            unblinded_psbt.unblind(
                PrivateKey(bpk))  # get values and blinding factors for inputs
            unblinded_psbt.blind(
                os.urandom(32))  # generate all blinding factors etc
            for i, out in enumerate(unblinded_psbt.outputs):
                if unblinded_psbt.outputs[i].blinding_pubkey:
                    out.reblind(b"1" * 32,
                                unblinded_psbt.outputs[i].blinding_pubkey,
                                b"test message")

            # remove stuff that Core doesn't like
            for inp in unblinded_psbt.inputs:
                inp.value = None
                inp.asset = None
                inp.value_blinding_factor = None
                inp.asset_blinding_factor = None

            for out in unblinded_psbt.outputs:
                if out.is_blinded:
                    out.asset = None
                    out.asset_blinding_factor = None
                    out.value = None
                    out.value_blinding_factor = None

            psbt = unblinded_psbt
        # use rpc to blind transaction
        else:
            try:  # master branch
                blinded = w.blindpsbt(unsigned)
            except:
                blinded = w.walletprocesspsbt(unsigned)['psbt']

            psbt = PSBT.from_string(blinded)

        psbt.sign_with(root)
        final = rpc.finalizepsbt(str(psbt))
        if final["complete"]:
            raw = final["hex"]
        else:
            print("WARNING: finalize failed, trying with embit")
            tx = finalize_psbt(psbt)
            raw = str(tx)
        # test accept
        res = rpc.testmempoolaccept([raw])
        self.assertTrue(res[0]["allowed"])
        if selfblind:
            # check we can reblind all outputs
            import json
            raw = w.unblindrawtransaction(raw)["hex"]
            decoded = w.decoderawtransaction(raw)
            self.assertEqual(
                len(decoded["vout"]) -
                sum([int("value" in out) for out in decoded["vout"]]), 1)
Пример #13
0
    def walletcreatefundedpsbt(self,
                               inputs,
                               outputs,
                               *args,
                               blind=True,
                               **kwargs):
        """
        Creates and blinds an Elements PSBT transaction.
        Arguments:
        1. inputs: [{txid, vout[, sequence, pegin stuff]}]
        2. outputs: [{address: amount, "asset": asset}, ...] # TODO: add assets support
        3. locktime = 0
        4. options {includeWatching, changeAddress, subtractFeeFromOutputs,
                    replaceable, add_inputs, feeRate, fee_rate}
        5. bip32 derivations
        6. solving data
        7. blind = True - Specter-LiquidRPC specific thing - blind transaction after creation
        """
        res = super().__getattr__("walletcreatefundedpsbt")(inputs, outputs,
                                                            *args, **kwargs)
        psbt = res.get("psbt", None)
        # check if we should blind the transaction
        if psbt and blind:
            # check that change is also blinded - fixes a bug in pset branch
            tx = PSET.from_string(psbt)
            der = None
            changepos = res.get("changepos", None)
            if changepos is not None and len(args) >= 2:
                addr = args[1].get("changeAddress", None)
                if addr:
                    _, bpub = addr_decode(addr)
                    der = tx.outputs[changepos].bip32_derivations
                    if bpub and (tx.outputs[changepos].blinding_pubkey is
                                 None):
                        tx.outputs[changepos].blinding_pubkey = bpub.sec()
                    res["psbt"] = str(tx)
                    psbt = str(tx)

            # generate all blinding stuff ourselves in deterministic way
            bpk = bytes.fromhex(self.dumpmasterblindingkey())
            tx.unblind(
                PrivateKey(bpk))  # get values and blinding factors for inputs
            seed = tagged_hash("liquid/blinding_seed", bpk)
            tx.blind(seed)  # generate all blinding factors etc
            # proprietary fields for Specter - 00 is global blinding seed
            tx.unknown[b"\xfc\x07specter\x00"] = seed

            # reblind and encode nonces in change output
            if changepos is not None:
                txseed = tx.txseed(seed)
                # blinding seed to calculate per-output nonces
                message = b"\x01\x00\x20" + txseed
                for i, out in enumerate(tx.outputs):
                    # skip unblinded and change address itself
                    if out.blinding_pubkey is None or i == changepos:
                        continue
                    # key 01<i> is blinding pubkey for output i
                    message += b"\x05\x01" + i.to_bytes(4, "little")
                    # message is blinding pubkey
                    message += bytes([len(out.blinding_pubkey)
                                      ]) + out.blinding_pubkey
                # extra message for rangeproof - proprietary field
                tx.outputs[changepos].unknown[b"\xfc\x07specter\x01"] = message
                # re-generate rangeproof with extra message
                nonce = tagged_hash("liquid/range_proof",
                                    txseed + changepos.to_bytes(4, "little"))
                tx.outputs[changepos].reblind(nonce, extra_message=message)

            res["psbt"] = str(tx)
        return res
Пример #14
0
    def create_psbts(self, base64_psbt, wallet):
        # liquid transaction
        if base64_psbt.startswith("cHNl"):
            # remove rangeproofs and add sighash alls
            psbt = PSET.from_string(base64_psbt)
            # make sure we have tx blinding seed in the transaction
            if psbt.unknown.get(b"\xfc\x07specter\x00"):
                for out in psbt.outputs:
                    out.range_proof = None
                    # out.surjection_proof = None
                    # we know assets - we can blind it
                    if out.asset:
                        out.asset_commitment = None
                        out.asset_blinding_factor = None
                    # we know value - we can blind it
                    if out.value:
                        out.value_commitment = None
                        out.value_blinding_factor = None
                for inp in psbt.inputs:
                    if inp.value and inp.asset:
                        inp.range_proof = None
        else:
            psbt = PSBT.from_string(base64_psbt)

        fill_external_wallet_derivations(psbt, wallet)

        base64_psbt = psbt.to_string()
        psbts = super().create_psbts(base64_psbt, wallet)
        # remove non-witness utxo if they are there to reduce QR code size
        updated_psbt = wallet.fill_psbt(base64_psbt,
                                        non_witness=False,
                                        xpubs=False,
                                        taproot_derivations=True)
        try:
            qr_psbt = PSBT.from_string(updated_psbt)
        except:
            qr_psbt = PSET.from_string(updated_psbt)
        # find my key
        fgp = None
        derivation = None
        for k in wallet.keys:
            if k in self.keys and k.fingerprint and k.derivation:
                fgp = bytes.fromhex(k.fingerprint)
                derivation = bip32.parse_path(k.derivation)
                break
        # remove unnecessary derivations from inputs and outputs
        for inp in qr_psbt.inputs + qr_psbt.outputs:
            # keep only one derivation path (idealy ours)
            found = False
            pubkeys = list(inp.bip32_derivations.keys())
            for i, pub in enumerate(pubkeys):
                if fgp and inp.bip32_derivations[pub].fingerprint != fgp:
                    # only pop if we already saw our derivation
                    # or if it's not the last one
                    if found or i < len(pubkeys) - 1:
                        inp.bip32_derivations.pop(k, None)
                else:
                    found = True
        # remove scripts from outputs (DIY should know about the wallet)
        for out in qr_psbt.outputs:
            out.witness_script = None
            out.redeem_script = None
        # remove partial sigs from inputs
        for inp in qr_psbt.inputs:
            inp.partial_sigs = {}
        psbts["qrcode"] = qr_psbt.to_string()

        # we can add xpubs to SD card, but non_witness can be too large for MCU
        psbts["sdcard"] = wallet.fill_psbt(base64_psbt,
                                           non_witness=False,
                                           xpubs=True,
                                           taproot_derivations=True)
        psbts["hwi"] = wallet.fill_psbt(base64_psbt,
                                        non_witness=False,
                                        xpubs=True,
                                        taproot_derivations=True)
        return psbts
Пример #15
0
    def walletcreatefundedpsbt(
        self, inputs, outputs, locktime=0, options={}, *args, blind=True, **kwargs
    ):
        """
        Creates and blinds an Elements PSBT transaction.
        Arguments:
        1. inputs: [{txid, vout[, sequence, pegin stuff]}]
        2. outputs: [{address: amount, "asset": asset}, ...] # TODO: add assets support
        3. locktime = 0
        4. options {includeWatching, changeAddress, subtractFeeFromOutputs,
                    replaceable, add_inputs, feeRate, fee_rate, changeAddresses}
        5. bip32 derivations
        6. solving data
        7. blind = True - Specter-LiquidRPC specific thing - blind transaction after creation
        """
        options = copy.deepcopy(options)
        change_addresses = (
            options.pop("changeAddresses") if "changeAddresses" in options else None
        )
        destinations = []
        for o in outputs:
            for k in o:
                if k != "asset":
                    destinations.append(addr_decode(k)[0])
        res = super().__getattr__("walletcreatefundedpsbt")(
            inputs, outputs, locktime, options, *args, **kwargs
        )
        psbt = res.get("psbt", None)

        # remove zero-output (bug in Elements)
        # TODO: remove after release
        if psbt:
            try:
                tx = PSET.from_string(psbt)
                # check if there are zero outputs
                has_zero = len([out for out in tx.outputs if out.value == 0]) > 0
                has_blinded = any([out.blinding_pubkey for out in tx.outputs])
                logger.error(has_zer, has_blinded)
                if has_blinded and has_zero:
                    tx.outputs = [out for out in tx.outputs if out.value > 0]
                    psbt = str(tx)
                    res["psbt"] = psbt
            except:
                pass

        # replace change addresses from the transactions if we can
        if change_addresses and psbt:
            try:
                tx = PSET.from_string(psbt)
                cur = 0
                for out in tx.outputs:
                    # fee
                    if out.script_pubkey.data == b"":
                        continue
                    # not change for sure
                    if not out.bip32_derivations:
                        continue
                    # do change replacement
                    if out.script_pubkey not in destinations:
                        sc, bkey = addr_decode(change_addresses[cur])
                        cur += 1
                        out.script_pubkey = sc
                        out.blinding_pubkey = bkey.sec() if bkey else None
                        out.bip32_derivations = {}
                        out.redeem_script = None
                        out.witness_script = None
                # fill derivation info
                patched = (
                    super().__getattr__("walletprocesspsbt")(str(tx), False).get("psbt")
                )
                patchedtx = PSET.from_string(patched)
                assert len(tx.outputs) == len(patchedtx.outputs)
                for out1, out2 in zip(tx.outputs, patchedtx.outputs):
                    # fee
                    if out1.script_pubkey.data == b"":
                        continue
                    # not change for sure
                    if not out2.bip32_derivations:
                        continue
                    # do change replacement
                    if out1.script_pubkey not in destinations:
                        out1.bip32_derivations = out2.bip32_derivations
                        out1.redeem_script = out2.redeem_script
                        out1.witness_script = out2.witness_script

                res["psbt"] = str(tx)
            except Exception as e:
                logger.error(e)
                raise e

        psbt = res.get("psbt", None)
        # check if we should blind the transaction
        if psbt and blind:
            # check that change is also blinded - fixes a bug in pset branch
            tx = PSET.from_string(psbt)
            changepos = res.get("changepos", None)
            # no change output
            if changepos < 0:
                changepos = None

            # generate all blinding stuff ourselves in deterministic way
            tx.unblind(
                self.master_blinding_key
            )  # get values and blinding factors for inputs
            seed = tagged_hash("liquid/blinding_seed", self.master_blinding_key.secret)
            try:
                tx.blind(seed)  # generate all blinding factors etc
                # proprietary fields for Specter - 00 is global blinding seed
                tx.unknown[b"\xfc\x07specter\x00"] = seed
            except PSBTError:
                seed = None

            # reblind and encode nonces in change output
            if seed and changepos is not None:
                txseed = tx.txseed(seed)
                # blinding seed to calculate per-output nonces
                message = b"\x01\x00\x20" + txseed
                for i, out in enumerate(tx.outputs):
                    # skip unblinded and change address itself
                    if out.blinding_pubkey is None or i == changepos:
                        continue
                    # key 01<i> is blinding pubkey for output i
                    message += b"\x05\x01" + i.to_bytes(4, "little")
                    # message is blinding pubkey
                    message += bytes([len(out.blinding_pubkey)]) + out.blinding_pubkey
                # extra message for rangeproof - proprietary field
                tx.outputs[changepos].unknown[b"\xfc\x07specter\x01"] = message
                # re-generate rangeproof with extra message
                nonce = tagged_hash(
                    "liquid/range_proof", txseed + changepos.to_bytes(4, "little")
                )
                tx.outputs[changepos].reblind(nonce, extra_message=message)

            res["psbt"] = str(tx)
        return res