Пример #1
0
 def test_grind(self):
     pk = PrivateKey(b"1" * 32)
     msgs = [bytes([i] * 32) for i in range(255)]
     for msg in msgs:
         sig = pk.sign(msg)
         der = sig.serialize()
         # low R is grinded
         self.assertTrue(len(der) <= 70)
Пример #2
0
 def test_pubkeys(self):
     valid_keys = [
         (
             b"1" * 32,
             True,
             "036930f46dd0b16d866d59d1054aa63298b357499cd1862ef16f3f55f1cafceb82",
         ),
         (
             b"\x00" * 31 + b"\x01",
             False,
             "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
         ),
     ]
     pub2 = PublicKey.from_string(
         "026930f46dd0b16d866d59d1054aa63298b357499cd1862ef16f3f55f1cafceb82"
     )
     for secret, compressed, sec in valid_keys:
         priv = PrivateKey(secret, compressed=compressed)
         pub = priv.get_public_key()
         # check str works
         str(priv)
         str(pub)
         self.assertEqual(str(pub), sec)
         self.assertEqual(pub, PublicKey.from_string(sec))
         pub.compressed = not pub.compressed
         # compressed and uncompressed are considered different now
         self.assertFalse(pub == PublicKey.from_string(sec))
         s = BytesIO()
         self.assertEqual(pub.write_to(s),
                          33 + 32 * int(not pub.compressed))
         self.assertEqual(priv.write_to(s), 32)
         # round trip
         self.assertEqual(PrivateKey.parse(priv.serialize()), priv)
         self.assertEqual(PublicKey.parse(pub.serialize()), pub)
         # sign random message
         msg = b"5" * 32
         sig = priv.sign(msg)
         self.assertTrue(pub.verify(sig, msg))
         # round trip
         self.assertEqual(Signature.parse(sig.serialize()), sig)
         # checks of the operators
         self.assertEqual(priv < pub, priv.sec() < pub.sec())
         self.assertEqual(priv > pub, priv.sec() > pub.sec())
         self.assertEqual(pub2 < pub, pub2 < priv)
         self.assertEqual(pub2 > pub, pub2 > priv)
         priv == priv
         pub == pub
         self.assertEqual(str(priv), priv.wif())
         self.assertEqual(str(priv), priv.to_base58())
         hash(priv)
         hash(pub)
Пример #3
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)
Пример #4
0
    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()
Пример #5
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
Пример #6
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
Пример #7
0
 def master_blinding_key(self):
     if self._master_blinding_key is None:
         self._master_blinding_key = PrivateKey(
             bytes.fromhex(self.dumpmasterblindingkey())
         )
     return self._master_blinding_key