keep-network/tbtc

A redemption with zero value is allowed by tbtc but rejected by the mempool

samczsun opened this issue · 14 comments

Description

Bitcoin rejects transactions that send "dust", and an output with zero sats very comfortably meets the definition of dust. Such a transaction isn't rejected by tbtc, creating a discrepancy which can be exploited to exchange BTC for signer ETH.

Exploit Scenario

Eve creates a deposit and requests a redemption with 0 value. The signers are unable to get this transaction included in a block without going directly to a miner. Assuming they fail to do so, Eve gets 150% of her deposit back in the form of ETH.

Suggested Remediation

A simple solution would be to implement an upper cap on fees. A better one might be to reimplement the dust calculations, although the dust threshold is set on a per-node basis. As both solutions are slightly brittle, a third solution might be to redesign the system such that invalid transactions that can't be accepted into the mempool can be used to penalize the depositor in some way.

The current thought is to set an upper bound of lotSize - initialRedemptionFee, That would make the value always >= minimumRedemptionFee (currently 2000 sat)
@prestwich would love to run this by you for a sanity check
@Shadowfiend, in case I missed anything

What's the failure behavior here? Who loses funds if the tx never makes it on chain, or the UTXO is 100% sent to fees?

Signers do (as it manifests as signer fraud, since the signers cannot further bump the fee and they become subject to the redemption timeout).

On the other hand, there's nothing stopping the signers from broadcasting the tx with a lower fee… The fee in the deposit acts as an upper bound. So actually I don't think this is a viable attack.

Ah, there is something stopping them: the signature must be authorized or the signers risk bond loss. So if the redemption request is submitted with a starting fee at a high value, the deposit may be unredeemable, fees unchangeable, and the signers may forfeit their bonds. Signers can still take the BTC with whatever fee value they want once they lose their bond, but it's not a great scenario.

With this in mind, it seems like the right move here is to set an upper bound on the fee (as suggested by @NicholasDotSol ) but only in requestRedemption. Thoughts?

Stepping back from mechanics for a second, the motivation for the current design was that the loop would NEVER exceed the UTXO value, because we considered the odds that fees would eclipse lot size (1 BTC) to be negligible. like LN, the security model breaks if fees are higher than the amount of BTC being secured

So it's not as simple as just adding a maximum. we have to decide who should pay if the maximum is reached. I'd lean towards having the redeemer pay

Hmmm… I'm not sure I'm following here. In a standard scenario where the fee starts low and is fee-bumped to be high, the redeemer does pay, insofar as we expect the redeemer to broadcast the tx as soon as it can be accepted so they can get their funds with minimum loss, or the signers to do so in order to free up their bonds and avoid punishment. In that scenario, if the fee is eventually bumped to any maximum, be it the total UTXO value or some amount less than that, all fee values between the initial value and the max value have valid, authorized signatures, and any of those signatures can be used to broadcast + prove the transaction safely, by signers or redeemer.

The specific scenario in this issue is the redeemer's full control over what the initial fee is, and the fact that a high enough initial fee allows for the initial transaction, without any fee bumps, to have too small a value and therefore be rejected. If we set an upper limit on the initial fee, the redemption flow won't start in the first place.

if the fee is eventually bumped to any maximum

my question is "then what happens." After the fee has been bumped to maximum, what happens?

Two options: anyone (e.g., signers or redeemer) broadcasts the transaction to Bitcoin with any of the fee steps and proves the outcome (or short-circuits the fee bumping loop in the first place by doing this before any max is hit), or the redemption proof timeout expires and the redeemer has the option of claiming signer bonds.

or the redemption proof timeout expires and the redeemer has the option of claiming signer bonds.

So if the maximum is lower than the network fee rate, the signers lose. If you want to bound the fee bumping loop, I would make it the opposite to avoid punishing signers for something outside their control

If the max is lower than the network fee rate everyone loses, though the signers lose more due to overcollateralization---at this point the redeemer has already given up their TBTC to initiate redemption.

I think I wasn't clear, though: I'm not suggesting bounding the fee bumping loop anymore. I'm only suggesting bounding its starting point, since that's the attack vector here.


That does leave us with the effective bound that already exists, of course, which is UTXO value - initial fee. As such, if the initial value of the fee is >1/2 lot size, the fee bump loop is effectively disabled (because the first bump would result in a fee > UTXO value, since fee bumps must be in steps = initial fee).

I think there's room to handle the situation you're describing by saying that IF the latest fee is >= (UTXO value - initial fee) (or whatever upper bound) AND a signature has been provided for that fee, THEN the signers are allowed to close the deposit unilaterally without a transaction proof and reclaim their bond. That would require tracking one additional boolean for whether the last fee has a corresponding signature (currently provided signatures are only emitted as events, they aren't tracked in the deposit), and it would add a bit of extra complexity to the state machine for redemption. I'm a bit loath to change the mechanism of proof timeout this late in the game for a limitation that already existed, but if we're feeling like this is an attack vector I think it's fairly manageable. It needs a little more study to see if any new attacks open up by having this proofless exit available for signers, though.

That does leave us with the effective bound that already exists, of course, which is UTXO value - initial fee. As such, if the initial value of the fee is >1/2 lot size, the fee bump loop is effectively disabled (because the first bump would result in a fee > UTXO value, since fee bumps must be in steps = initial fee).

Yes, this is exactly what I'm saying. If the next fee is ever > than the UTXO size we've entered a failure case. We previously assumed this could never happen because the UTXO was 1 BTC (but that may not have been a good assumption without a maximum initial fee). This failure case would result in some valid txn being signed sending BTC to the user, and then potentially a slashable failure to bump the fee after that. That would cause the signers to forfeit BTC and bond despite behaving honestly.

Ok, here's an updated proposal which handles some of the concerns, but not all of them.

First, define a minimum post-fee UTXO value, minimumUtxoValue. This is the minimum value that the contracts will accept as utxoValue - fee.

Second, disallow an initial fee that is >= utxoValue / 2.

Third, if a fee bump would result in a post-fee UTXO value < the minimum, permit the fee bump, clamped to the minimum.

Fourth, if needed, adjust the minimum permitted lot size (currently 50k sats).


This approach doesn't really fix the problem---there is still a failure case where the minimumUtxoValue prevents a high enough fee from being used, and in this case the signer will still forfeit their bond and (presumably when fees settle down) the BTC. However, it also doesn't require a proofless exit, which feels like it needs more study (and may be a good idea for v2), and with appropriate parameters it should minimize the probability of such an event, even if it doesn't completely eliminate it.

In particular, if the minimum UTXO value is 2000 sats, and the minimum available lot size is 50000 sats, the occurrence would require a fee event that would, at minimum, cause loss of 96% of the UTXO value in fees. An honest redeemer can be expected to start their redemption at the lowest reasonable fee value, and to avoid high-fee scenarios if possible. A dishonest redeemer could push for high fees to grief, but assuming the minimum lot size of 50k sats is available and the minimum UTXO value is 2k sats, my understanding is that would require fees of ~480 sats/vbyte, sustained for ~5 hours (due to the limited initial fee + allowing an additional fee bump, and assuming an hour to get the requisite confirmations). If this feels insufficient, we can raise the minimum allowed lot size to also raise the max fee.

Worth noting: there is an effective (non-governance) minimum lot size of over 100k sats at the moment due to the ETH/BTC price and #634 .

Second, disallow an initial fee that is < utxoValue / 2.

Do you mean >= utxoValue / 2? i.e. it must be possible to bump at least 1 time?

Other than that this seems pretty reasonable.

Hah, yep, updating.