def _unblind(self):
        if not self.descriptor.is_blinded:
            return

        b = self.tx

        mbpk = self.descriptor.blinding_key.key
        net = self.network

        values = [0 for out in b.vout]
        assets = [b"\xFF" * 32 for out in b.vout]
        datas = []
        # search for datas encoded in rangeproofs
        for i, out in enumerate(b.vout):
            # unblinded
            if isinstance(out.value, int):
                values[i] = out.value
                assets[i] = out.asset
                continue

            pk = slip77.blinding_key(mbpk, out.script_pubkey)
            try:
                res = out.unblind(pk.secret, message_length=1000)
                value, asset, vbf, abf, extra, *_ = res
                if len(extra.rstrip(b"\x00")) > 0:
                    datas.append(extra)
                values[i] = value
                assets[i] = asset
            except Exception as e:
                logger.warn(e)  # TODO: remove, it's ok
                pass

        # to calculate blinding seed
        tx = PSET(b)
        seed = tagged_hash("liquid/blinding_seed", mbpk.secret)
        txseed = tx.txseed(seed)
        pubkeys = {}

        for extra in datas:
            s = BytesIO(extra)
            while True:
                k = read_string(s)
                if len(k) == 0:
                    break
                v = read_string(s)
                if k[0] == 1 and len(k) == 5:
                    idx = int.from_bytes(k[1:], "little")
                    pubkeys[idx] = v
                elif k == b"\x01\x00":
                    txseed = v

        for i, out in enumerate(b.vout):
            if out.witness.range_proof.is_empty:
                continue
            if i in pubkeys and len(pubkeys[i]) in [33, 65]:
                nonce = tagged_hash("liquid/range_proof",
                                    txseed + i.to_bytes(4, "little"))
                if out.ecdh_pubkey == PrivateKey(nonce).sec():
                    try:
                        res = unblind(
                            pubkeys[i],
                            nonce,
                            out.witness.range_proof.data,
                            out.value,
                            out.asset,
                            out.script_pubkey,
                        )
                        value, asset, vbf, abf, extra, min_value, max_value = res
                        assets[i] = asset
                        values[i] = value
                    except Exception as e:
                        logger.warn(f"Failed at unblinding output {i}: {e}")
                else:
                    logger.warn(f"Failed at unblinding: {e}")

        for i, out in enumerate(b.vout):
            out.asset = assets[i]
            out.value = values[i]
            out.witness = TxOutWitness()
Exemple #2
0
    def decoderawtransaction(self, tx):
        unblinded = self.unblindrawtransaction(tx)["hex"]
        obj = super().__getattr__("decoderawtransaction")(unblinded)
        try:
            # unblind the rest of outputs
            b = LTransaction.from_string(tx)

            mbpk = PrivateKey(bytes.fromhex(self.dumpmasterblindingkey()))
            net = get_network(self.getblockchaininfo().get("chain"))

            outputs = obj["vout"]
            datas = []
            fee = 0
            # search for datas encoded in rangeproofs
            for i, out in enumerate(b.vout):
                o = outputs[i]
                if isinstance(out.value, int):
                    if "value" in o:
                        assert o["value"] == round(out.value * 1e-8, 8)
                    else:
                        o["value"] = round(out.value * 1e-8, 8)
                    if "asset" in o:
                        assert o["asset"] == bytes(reversed(
                            out.asset[-32:])).hex()
                    else:
                        o["asset"] = bytes(reversed(out.asset[-32:])).hex()
                    try:
                        o["scriptPubKey"]["addresses"] = [
                            liquid_address(out.script_pubkey, network=net)
                        ]
                    except:
                        pass
                    if out.script_pubkey.data == b"":
                        # fee negative?
                        fee -= out.value

                pk = slip77.blinding_key(mbpk, out.script_pubkey)
                try:
                    res = out.unblind(pk.secret, message_length=1000)
                    value, asset, vbf, abf, extra, min_value, max_value = res
                    if "value" in o:
                        assert o["value"] == round(value * 1e-8, 8)
                    else:
                        o["value"] = round(value * 1e-8, 8)
                    if "asset" in o:
                        assert o["asset"] == bytes(reversed(asset[-32:])).hex()
                    else:
                        o["asset"] = bytes(reversed(asset[-32:])).hex()
                    try:
                        o["scriptPubKey"]["addresses"] = [
                            liquid_address(out.script_pubkey, pk, network=net)
                        ]
                    except:
                        pass
                    if len(extra.rstrip(b"\x00")) > 0:
                        datas.append(extra)
                except Exception as e:
                    pass

            # should be changed with seed from tx
            tx = PSET(b)
            seed = tagged_hash("liquid/blinding_seed", mbpk.secret)
            txseed = tx.txseed(seed)
            pubkeys = {}

            for extra in datas:
                s = BytesIO(extra)
                while True:
                    k = read_string(s)
                    if len(k) == 0:
                        break
                    v = read_string(s)
                    if k[0] == 1 and len(k) == 5:
                        idx = int.from_bytes(k[1:], "little")
                        pubkeys[idx] = v
                    elif k == b"\x01\x00":
                        txseed = v

            for i, out in enumerate(outputs):
                o = out
                if i in pubkeys and len(pubkeys[i]) in [33, 65]:
                    nonce = tagged_hash("liquid/range_proof",
                                        txseed + i.to_bytes(4, "little"))
                    if b.vout[i].ecdh_pubkey == PrivateKey(nonce).sec():
                        try:
                            res = unblind(
                                pubkeys[i],
                                nonce,
                                b.vout[i].witness.range_proof.data,
                                b.vout[i].value,
                                b.vout[i].asset,
                                b.vout[i].script_pubkey,
                            )
                            value, asset, vbf, abf, extra, min_value, max_value = res
                            if "value" in o:
                                assert o["value"] == round(value * 1e-8, 8)
                            else:
                                o["value"] = round(value * 1e-8, 8)
                            if "asset" in o:
                                assert o["asset"] == bytes(
                                    reversed(asset[-32:])).hex()
                            else:
                                o["asset"] = bytes(reversed(asset[-32:])).hex()
                            try:
                                o["scriptPubKey"]["addresses"] = [
                                    liquid_address(
                                        b.vout[i].script_pubkey,
                                        PublicKey.parse(pubkeys[i]),
                                        network=net,
                                    )
                                ]
                            except:
                                pass
                        except Exception as e:
                            logger.warn(
                                f"Failed at unblinding output {i}: {e}")
                    else:
                        logger.warn(f"Failed at unblinding: {e}")
            if fee != 0:
                obj["fee"] = round(-fee * 1e-8, 8)
        except Exception as e:
            logger.warn(f"Failed at unblinding transaction: {e}")
        return obj