Exemplo n.º 1
0
 def __contains__(self, addr):
     """finds address by confidential or unconfidential address by converting to scriptpubkey"""
     try:  # can fail if addr is "Fee", "Dummy" or hex-scriptpubkey
         sc, _ = addr_decode(addr)
         if sc and self._scripts.__contains__(sc):
             return True
     except:
         pass
     return super().__contains__(addr)
Exemplo n.º 2
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)
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
0
 def __getitem__(self, addr):
     """finds address by confidential or unconfidential address by converting to scriptpubkey"""
     sc, _ = addr_decode(addr)
     if sc in self._scripts:
         return self._scripts[sc]
     return super().__getitem__(addr)
Exemplo n.º 6
0
 def _update_scripts(self):
     for addr in list(self.keys()):
         sc, _ = addr_decode(addr)
         if sc and sc not in self._scripts:
             self._scripts[sc] = super().__getitem__(addr)