コード例 #1
0
    def get_fidelity_bond_template(self):
        if not isinstance(self.wallet_service.wallet, FidelityBondMixin):
            jlog.info(
                "Not a fidelity bond wallet, not announcing fidelity bond")
            return None
        blocks = jm_single().bc_interface.get_current_block_height()
        mediantime = jm_single().bc_interface.get_best_block_median_time()

        BLOCK_COUNT_SAFETY = 2  #use this safety number to reduce chances of the proof expiring
        #before the taker gets a chance to verify it
        RETARGET_INTERVAL = 2016
        CERT_MAX_VALIDITY_TIME = 1
        cert_expiry = ((blocks + BLOCK_COUNT_SAFETY) //
                       RETARGET_INTERVAL) + CERT_MAX_VALIDITY_TIME

        utxos = self.wallet_service.wallet.get_utxos_by_mixdepth(
            include_disabled=True,
            includeheight=True)[FidelityBondMixin.FIDELITY_BOND_MIXDEPTH]
        timelocked_utxos = [
            (outpoint, info) for outpoint, info in utxos.items()
            if FidelityBondMixin.is_timelocked_path(info["path"])
        ]
        if len(timelocked_utxos) == 0:
            jlog.info(
                "No timelocked coins in wallet, not announcing fidelity bond")
            return
        timelocked_utxos_with_confirmation_time = [
            (outpoint, info, jm_single().bc_interface.get_block_time(
                jm_single().bc_interface.get_block_hash(info["height"])))
            for (outpoint, info) in timelocked_utxos
        ]

        interest_rate = get_interest_rate()
        max_valued_bond = max(
            timelocked_utxos_with_confirmation_time,
            key=lambda x: FidelityBondMixin.
            calculate_timelocked_fidelity_bond_value(x[1]["value"], x[2], x[1][
                "path"][-1], mediantime, interest_rate))
        (utxo_priv,
         locktime), engine = self.wallet_service.wallet._get_key_from_path(
             max_valued_bond[1]["path"])
        utxo_pub = engine.privkey_to_pubkey(utxo_priv)
        cert_priv = os.urandom(32) + b"\x01"
        cert_pub = btc.privkey_to_pubkey(cert_priv)
        cert_msg = b"fidelity-bond-cert|" + cert_pub + b"|" + str(
            cert_expiry).encode("ascii")
        cert_sig = base64.b64decode(btc.ecdsa_sign(cert_msg, utxo_priv))
        utxo = (max_valued_bond[0][0], max_valued_bond[0][1])
        fidelity_bond = FidelityBond(utxo, utxo_pub, locktime, cert_expiry,
                                     cert_priv, cert_pub, cert_sig)
        jlog.info("Announcing fidelity bond coin {}".format(fmt_utxo(utxo)))
        return fidelity_bond
コード例 #2
0
def get_fidelity_bond_data(taker):
    with taker.dblock:
        fbonds = taker.db.execute("SELECT * FROM fidelitybonds;").fetchall()

    blocks = jm_single().bc_interface.get_current_block_height()
    mediantime = jm_single().bc_interface.get_best_block_median_time()
    interest_rate = get_interest_rate()

    bond_utxo_set = set()
    fidelity_bond_data = []
    bond_outpoint_conf_times = []
    fidelity_bond_values = []
    for fb in fbonds:
        try:
            parsed_bond = FidelityBondProof.parse_and_verify_proof_msg(
                fb["counterparty"], fb["takernick"], fb["proof"])
        except ValueError:
            continue
        bond_utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
            parsed_bond.utxo, parsed_bond.utxo_pub, parsed_bond.locktime,
            parsed_bond.cert_expiry, blocks)
        if bond_utxo_data == None:
            continue
        #check for duplicated utxos i.e. two or more makers using the same UTXO
        # which is obviously not allowed, a fidelity bond must only be usable by one maker nick
        utxo_str = parsed_bond.utxo[0] + b":" + str(
            parsed_bond.utxo[1]).encode("ascii")
        if utxo_str in bond_utxo_set:
            continue
        bond_utxo_set.add(utxo_str)

        fidelity_bond_data.append((parsed_bond, bond_utxo_data))
        conf_time = jm_single().bc_interface.get_block_time(
            jm_single().bc_interface.get_block_hash(
                blocks - bond_utxo_data["confirms"] + 1))
        bond_outpoint_conf_times.append(conf_time)

        bond_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
            bond_utxo_data["value"], conf_time, parsed_bond.locktime,
            mediantime, interest_rate)
        fidelity_bond_values.append(bond_value)
    return (fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times)
コード例 #3
0
def get_bond_values(amount: int,
                    months: int,
                    confirm_time: Optional[float] = None,
                    interest: Optional[float] = None,
                    exponent: Optional[float] = None,
                    orderbook: Optional[Mapping[str, Any]] = None) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
    """
    Conveniently generate values [and statistics] for multiple possible fidelity bonds.

    Args:
        amount: Fidelity bond UTXO amount in satoshi
        months: For how many months to calculate the results
        confirm_time: Fidelity bond UTXO confirmation time as timestamp, if None, current time is used.
                      I.e., like if the fidelity bond UTXO with given amount has just confirmed on the blockchain.
        interest: Interest rate, if None, value is taken from config
        exponent: Exponent, if None, value is taken from config
        orderbook: Orderbook data, if given, additional statistics are included in the results.
    Returns:
        A tuple with 2 elements.
         First is a dictionary with all the parameters used to perform fidelity bond calculations.
         Second is a list of dictionaries, one for each month, with the results.
    """
    current_time = datetime.now().timestamp()
    if confirm_time is None:
        confirm_time = current_time
    if interest is None:
        interest = get_interest_rate()
    if exponent is None:
        exponent = jm_single().config.getfloat("POLICY", "bond_value_exponent")
        use_config_exp = True
    else:
        old_exponent = jm_single().config.get("POLICY", "bond_value_exponent")
        jm_single().config.set("POLICY", "bond_value_exponent", str(exponent))
        use_config_exp = False
    if orderbook:
        bond_values = [fb["bond_value"] for fb in orderbook["fidelitybonds"]]
        bonds_sum = sum(bond_values)
        percentiles = quantiles(bond_values, n=100, method="inclusive")

    parameters = {
        "amount": amount,
        "confirm_time": confirm_time,
        "current_time": current_time,
        "interest": interest,
        "exponent": exponent,
    }
    locktime = get_next_locktime(datetime.fromtimestamp(current_time))
    results = []
    for _ in range(months):
        fb_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
            amount,
            confirm_time,
            locktime.timestamp(),
            current_time,
            interest,
        )
        result = {"locktime": locktime.timestamp(),
                  "value": fb_value}
        if orderbook:
            result["weight"] = fb_value / (bonds_sum + fb_value)
            result["percentile"] = 100 - bisect_left(percentiles, fb_value)
        results.append(result)
        locktime = get_next_locktime(locktime)
    if not use_config_exp:
        # We don't want the modified exponent value to persist in memory, so we reset to whatever it was before
        jm_single().config.set("POLICY", "bond_value_exponent", old_exponent)
    return parameters, results
コード例 #4
0
    def create_orderbook_table(self, btc_unit, rel_unit):
        result = ''
        try:
            self.taker.dblock.acquire(True)
            rows = self.taker.db.execute('SELECT * FROM orderbook;').fetchall()
        finally:
            self.taker.dblock.release()
        if not rows:
            return 0, result
        rows = [o for o in rows if o["ordertype"] in filtered_offername_list]

        if jm_single().bc_interface == None:
            for row in rows:
                row["bondvalue"] = "No data"
        else:
            blocks = jm_single().bc_interface.get_current_block_height()
            mediantime = jm_single().bc_interface.get_best_block_median_time()
            interest_rate = get_interest_rate()
            for row in rows:
                with self.taker.dblock:
                    fbond_data = self.taker.db.execute(
                        "SELECT * FROM fidelitybonds WHERE counterparty=?;",
                        (row["counterparty"], )).fetchall()
                if len(fbond_data) == 0:
                    row["bondvalue"] = "0"
                    continue
                else:
                    try:
                        parsed_bond = FidelityBondProof.parse_and_verify_proof_msg(
                            fbond_data[0]["counterparty"],
                            fbond_data[0]["takernick"], fbond_data[0]["proof"])
                    except ValueError:
                        row["bondvalue"] = "0"
                        continue
                    utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
                        parsed_bond.utxo, parsed_bond.utxo_pub,
                        parsed_bond.locktime, parsed_bond.cert_expiry, blocks)
                    if utxo_data == None:
                        row["bondvalue"] = "0"
                        continue
                    bond_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
                        utxo_data["value"],
                        jm_single().bc_interface.get_block_time(
                            jm_single().bc_interface.get_block_hash(
                                blocks - utxo_data["confirms"] + 1)),
                        parsed_bond.locktime, mediantime, interest_rate)
                    row["bondvalue"] = satoshi_to_unit_power(
                        bond_value, 2 * unit_to_power[btc_unit])

        order_keys_display = (('ordertype', ordertype_display),
                              ('counterparty', do_nothing), ('oid', order_str),
                              ('cjfee', cjfee_display),
                              ('txfee', satoshi_to_unit), ('minsize',
                                                           satoshi_to_unit),
                              ('maxsize', satoshi_to_unit), ('bondvalue',
                                                             do_nothing))

        # somewhat complex sorting to sort by cjfee but with swabsoffers on top

        def orderby_cmp(x, y):
            if x['ordertype'] == y['ordertype']:
                return cmp(Decimal(x['cjfee']), Decimal(y['cjfee']))
            return cmp(offername_list.index(x['ordertype']),
                       offername_list.index(y['ordertype']))

        for o in sorted(rows, key=cmp_to_key(orderby_cmp)):
            result += ' <tr>\n'
            for key, displayer in order_keys_display:
                result += '  <td>' + displayer(o[key], o, btc_unit,
                                               rel_unit) + '</td>\n'
            result += ' </tr>\n'
        return len(rows), result
コード例 #5
0
    def create_sybil_resistance_page(self, btc_unit):
        if jm_single().bc_interface == None:
            return "", "Calculations unavailable, requires configured bitcoin node."

        (fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times) =\
            get_fidelity_bond_data(self.taker)

        choose_units_form = (
            '<form method="get" action="">' +
            '<select name="btcunit" onchange="this.form.submit();">' + ''.join(
                ('<option>' + u + ' </option>'
                 for u in sorted_units)) + '</select></form>')
        choose_units_form = choose_units_form.replace(
            '<option>' + btc_unit, '<option selected="selected">' + btc_unit)
        mainbody = choose_units_form

        honest_weight = sum(fidelity_bond_values)
        mainbody += (
            "Assuming the makers in the offerbook right now are not sybil attackers, "
            +
            "how much would a sybil attacker starting now have to sacrifice to succeed in their"
            + " attack with 95% probability. Honest weight=" +
            satoshi_to_unit_power(honest_weight, 2 * unit_to_power[btc_unit]) +
            " " + btc_unit + "<sup>" + bond_exponent +
            "</sup><br/>Also assumes that takers " +
            "are not price-sensitive and that their max " +
            "coinjoin fee is configured high enough that they dont exclude any makers."
        )
        heading2 = "Sybil attacks from external enemies."

        mainbody += ('<table class="tftable" border="1"><tr>' +
                     '<th>Maker count</th>' + '<th>6month locked coins / ' +
                     btc_unit + '</th>' + '<th>1y locked coins / ' + btc_unit +
                     '</th>' + '<th>2y locked coins / ' + btc_unit + '</th>' +
                     '<th>5y locked coins / ' + btc_unit + '</th>' +
                     '<th>10y locked coins / ' + btc_unit + '</th>' +
                     '<th>Required burned coins / ' + btc_unit + '</th>' +
                     '</tr>')

        timelocks = [0.5, 1.0, 2.0, 5.0, 10.0, None]
        interest_rate = get_interest_rate()
        for makercount, unit_success_sybil_weight in sybil.successful_attack_95pc_sybil_weight.items(
        ):
            success_sybil_weight = unit_success_sybil_weight * honest_weight
            row = "<tr><td>" + str(makercount) + "</td>"
            for timelock in timelocks:
                if timelock != None:
                    coins_per_sybil = sybil.weight_to_locked_coins(
                        success_sybil_weight, interest_rate, timelock)
                else:
                    coins_per_sybil = sybil.weight_to_burned_coins(
                        success_sybil_weight)
                row += ("<td>" + satoshi_to_unit(coins_per_sybil * makercount,
                                                 None, btc_unit, 0) + "</td>")
            row += "</tr>"
            mainbody += row
        mainbody += "</table>"

        mainbody += (
            "<h2>Sybil attacks from enemies within</h2>Assume a sybil attack is ongoing"
            +
            " right now and that the counterparties with the most valuable fidelity bonds are "
            +
            " actually controlled by the same entity. Then, what is the probability of a "
            +
            " successful sybil attack for a given makercount, and what is the fidelity bond "
            +
            " value being foregone by not putting all bitcoins into just one maker."
        )
        mainbody += ('<table class="tftable" border="1"><tr>' +
                     '<th>Maker count</th>' + '<th>Success probability</th>' +
                     '<th>Foregone value / ' + btc_unit + '<sup>' +
                     bond_exponent + '</sup></th>' + '</tr>')

        #limited because calculation is slow, so this avoids server being too slow to respond
        MAX_MAKER_COUNT_INTERNAL = 10
        weights = sorted(fidelity_bond_values)[::-1]
        for makercount in range(1, MAX_MAKER_COUNT_INTERNAL + 1):
            makercount_str = (
                str(makercount) + " - " + str(MAX_MAKER_COUNT_INTERNAL)
                if makercount == len(fidelity_bond_data)
                and len(fidelity_bond_data) != MAX_MAKER_COUNT_INTERNAL else
                str(makercount))
            success_prob = sybil.calculate_top_makers_sybil_attack_success_probability(
                weights, makercount)
            total_sybil_weight = sum(weights[:makercount])
            sacrificed_values = [
                sybil.weight_to_burned_coins(w) for w in weights[:makercount]
            ]
            foregone_value = (
                sybil.coins_burned_to_weight(sum(sacrificed_values)) -
                total_sybil_weight)
            mainbody += ("<tr><td>" + makercount_str + "</td><td>" +
                         str(round(success_prob * 100.0, 5)) +
                         "%</td><td>" + satoshi_to_unit_power(
                             foregone_value, 2 * unit_to_power[btc_unit]) +
                         "</td></tr>")
            if makercount == len(weights):
                break
        mainbody += "</table>"

        return heading2, mainbody