monero-project / research-lab

A general repo for Monero Research Lab work in progress and completed work
238 stars 77 forks source link

Eliminating the 10-block-lock #95

Open UkoeHB opened 2 years ago

UkoeHB commented 2 years ago

Eliminating the 10-block-lock

This is a proposal for eliminating Monero's 10-block-lock, which prevents users from spending outputs until they are 10 blocks old. The proposal has two significant and clear drawbacks, but I believe it is the best possible solution given all the constraints.

If you are reading this, please first look at it academically before dismissing it out of hand. Yes, the drawbacks may be too severe, but before passing final judgement I would like a useful discussion.

Motivation for 10-block-locks

In my view, there is one main reason for needing 10-block-locks in Monero.

Outputs in the PoW 'reorg zone' (the most recent \~1-10 blocks) can be completely removed from the chain by a reorg, because any output that can be reorged can be double-spent. If an attacker spams the network with double-spends of his own outputs, then on a regular basis 'naturally occurring' reorgs will cause some of his transactions to be replaced by his double-spends of those transactions' inputs. If an output created by the first group is used as a decoy by a random person, then that person's transaction will become invalid after the reorg.

In other words, decoys are vulnerable to the 'confirmation' problem. A tx author should only use decoys that are 'strongly confirmed', i.e. decoys that are buried below the 'reorg zone'.

Since all txs need strongly confirmed decoys, outputs in the 'reorg zone' will never be used as decoys. Hence, any output in the 'reorg zone' referenced by a tx input's ring signature must be that input's real spend. The tx author may be fine with revealing their real spend. However, if that output is used as a decoy in other users' txs, then since observers know it has been spent, they will know it is a decoy in those other users' txs. This weakens those users' ring signatures. To prevent that weakening, the 10-block-lock time is enforced. This way all decoys in a tx input are plausibly the real spend.

Proposal

  1. Allow transactions to have inputs with zero decoys if those inputs are very young (recently added to the chain).
  2. After a grace period, remove outputs from the pool of eligible ring members if they have been spent in a zero-decoy input.

This offers a compromise between fast-spends and minimizing the damage those fast-spends can do to ring signature effectiveness.

// constants
DEFAULT_SPENDABLE_AGE = 10;
INPUT_ELIGIBILITY_GRACE_BLOCKS = 20;
ZERO_DECOY_ELIGIBLE_ZONE = DEFAULT_SPENDABLE_AGE + INPUT_ELIGIBILITY_GRACE_BLOCKS;  //30
PROVABLY_SPENT_DECOY_ELIGIBLE_ZONE = ZERO_DECOY_ELIGIBLE_ZONE + INPUT_ELIGIBILITY_GRACE_BLOCKS; //50

fn validate_input_age_zero_decoy(block_height, input_height) -> bool
{
    // only outputs in the zero-decoy eligible zone can be spent with zero decoys
    if (block_height - input_height > ZERO_DECOY_ELIGIBLE_ZONE)
        return false;
    else
        return true;
}

fn validate_input_age(block_height, input_height, input) -> bool
{
    // only outputs that reached the default spendable age can be referenced in a membership proof with > 0 decoys (IF they are not 'provably spent and older than the provably spent eligible zone')
    if (block_height - input_height <= DEFAULT_SPENDABLE_AGE ||
        (block_height - input_height > PROVABLY_SPENT_DECOY_ELIGIBLE_ZONE && is_provably_spent(input))
        )
        return false;
    else
        return true;
}

A transaction that lands in the tx pool may not be added to a block right away. If a tx spends a zero-decoy input that is 10 blocks old at the time of construction, it can't even be added to the in-progress block if there is no grace period.

If a tx with a normal input references a provably-spent input that is 20 blocks old, and provably-spent in a tx that is 10 blocks old, then it can't be added to the in-progress block without a grace period on provably-spent ring members.

When constructing a transaction, if a real-spend is within the 10-block-lock, then spend it as a zero-decoy input. Otherwise use a ring signature like normal (however, the tx author should avoid using decoys that are already provably spent). The grace blocks should not be abused to make zero-decoy inputs that are older than 10 blocks.

Drawbacks

There are two main drawbacks to this proposal.

  1. When a transaction is constructed, any decoys selected from the most recent \~20 eligible blocks may become provably spent.
  2. Since input-type-eligibility changes as a function of the block height where a tx gets mined, if a tx lingers in the tx pool for longer than the 20-block grace period, it may become invalid.
    1. Any tx with a zero-decoy input would become invalid.
    2. Any tx with a normal input that references a provably-spent output would become invalid. If deterministic binning is implemented, it may be impossible for deterministic references to work across the eligibility-barrier (i.e. because output ordering/indexing would change within blocks after provably-spent outputs are removed). In that case, txs with normal inputs would always become invalid at the end of the grace period.

Note that Seraphis membership proof delegation allows tx authors to always complete membership proofs right before submitting their txs, which would make slow txs like multisig/atomic swaps compatible with this proposal (otherwise the grace period would usually expire before a tx can be submitted).

Looking at the future

If Monero implemented an ideal membership proof that could reference all outputs in the chain, then this proposal could be greatly simplified to 'allow zero-decoy inputs if the inputs are < 30 blocks old'.

iamsmooth commented 1 year ago

but the second transaction will typically never get propagated or mined

They can be propagated by a different part of the network and mined by another miner who saw them first, though usually this would only happen if they're generated close to the same time (or during a network fragmentation). For example, consider a poorly-designed redundant wallet backend where the same output is spent to perform the same payment from two different nodes using different decoys. Some dumb exchange might do this (exchanges are high on the list of entities doing dumb things at the network level, including even paying unnecessarily high fees). It costs them nothing but can pollute the network.

Note that this is different from actually propagating the double-spending transaction, which would strain the network without costing the attacker anything.

I think it is the same, as long as you don't propagate the triple spend. What you're propagating as a report is actually quite similar to the second transaction (continaing key image and signature), albeit abbreviated so lower absolute cost, but same principle.

Either way means the spammer can spam the network once every time they make a transaction. Not catastrophic, but it's a cost to consider.

One other thing, I don't think 1-conf is quite enough even with your proposal, if you just received the block. If the double spend and the block happen about the same time you may receive the block before getting the double spend notification. You would need to wait a "reasonable" amount of time.

tevador commented 1 year ago

They can be propagated by a different part of the network and mined by another miner who saw them first

In theory yes, but it never happens in practice.

Does it even matter if a double-spend is malicious or not? The outcome is the same and the defense mechanism should be the same.

I think it is the same, as long as you don't propagate the triple spend.

Validating the double-spend report is much faster than validating a full transaction (that includes range proofs, membership proofs etc.). The double-spend report is also smaller in size.

Either way means the spammer can spam the network once every time they make a transaction. Not catastrophic, but it's a cost to consider.

Yes, it's even listed in the drawbacks of my proposal.

You would need to wait a "reasonable" amount of time.

Yes, in practice you would only select decoys from blocks older than some threshold larger than the average time it takes to propagate a double-spend report.

iamsmooth commented 1 year ago

Does it even matter if a double-spend is malicious or not?

Mostly not, as long as you consider 'deliberate' cases, even if not "intended" as malicious as well as accidental ones. The latter could be viewed as a natural phenomenon with a probability (even if not exactly known), but the former would depend on someone's decisions. It might never happen ("in practice") for a while and then happen a lot if someone (a high volume transactor) decides to do something stupid. Malicious and stupid is about the same.

average time it takes to propagate a double-spend report.

What is the "average time" it takes if there is a network split? This is where you get into cases that aren't necessarily probabilistic in nature in the usual sense.

tevador commented 1 year ago

What is the "average time" it takes if there is a network split? This is where you get into cases that aren't necessarily probabilistic in nature in the usual sense.

In practice, there is a value that works in most cases and if it doesn't, the tx builder would just deal with the outcome. That's the price to pay for the elimination of the 10-conf rule.

iamsmooth commented 1 year ago

In practice, there is a value that works in most cases and if it doesn't, the tx builder would just deal with the outcome. That's the price to pay for the elimination of the 10-conf rule.

You can't assume that the tx builder actually cares about eliminating the 10 conf rule. Some will some won't. Remember, the context here was explaining why the 10 conf rule benefits privacy, and the listing "no negative effects on privacy" isn't actually clear. The benefit of the 10 conf rule accrues because everyone is willing to use the same rule for selecting decoys, removing any biases. If that breaks down, then privacy suffers (it may only be a little, but let's recognize that for what it is).

tevador commented 1 year ago

You can't assume that the tx builder actually cares about eliminating the 10 conf rule.

This is a research topic how to eliminate the 10-conf rule and I contributed by proposing a solution that does exactly that (without introducing provably spent outputs). Whether it should be done or not is another question.

the listing "no negative effects on privacy" isn't actually clear

I changed the wording to a more factually correct statement "Does not introduce provably spent outputs" to emphasize the main privacy benefit over the first proposal.

kayabaNerve commented 1 year ago

As a mitigation to the 10-block lock, we could implement input splitting.

Instead of proving input A exists in tree, we state input B exists as A[0]. Input B takes A's linking tag.

Then, input C doxxes itself as a split spend, and exists as A[1]. It has a distinct linking tag, and a balance of A - B.

This requires a range proof be added to input B, to make sure it doesn't exceed A's balance. Then C would need to prove the existence of A and B, making it twice as expensive and require we maintain a separate tree.

I don't think this is worthwhile, yet found it sufficiently novel, and accordingly worth commentating.