Timewarp attack 600 second grace period

Background

The original Great Consensus Cleanup soft fork proposal by @MattCorallo says the following:

  • Sadly, some deployed mining hardware relies on the ability to roll nTime forward by up to 600 seconds[3]. Thus, only requiring that the nTime field move forward during difficulty adjustment would allow a malicious miner to prevent some competitors from mining the next block by setting their timestamp to two hours in the future. Thus, we allow nTime to go backwards by 600 seconds, ensuring that even a block with a timestamp two hours in the future allows for 600 seconds of nTime rolling on the next block.

The footnote explains why 600 is probably the upper bound:

[3] While no official stratum specification exists, the btc.com pool server (one of the most popular pool servers today) rejects shares with timestamps more than 600 seconds in the future at btcpool-ABANDONED/src/bitcoin/StratumServerBitcoin.cc at e7c536834fd6785af7d7d68ff29111ed81209cdf Ā· btccom/btcpool-ABANDONED Ā· GitHub. While there are few resources describing hardware operation today, timestamp rolling can be observed on the chain (in some rare cases) as block timestamps go backwards when a miner rolled one block nTime forward and the next does not, but only incredibly rarely more than 600 seconds.

nTime rolling is also not likely to go away. Itā€™s potentially useful for ASIC devices that can go beyond 280 TH/s. As explained in sv2-spec/05-Mining-Protocol.md at 52e1fa22f68c343a3d25a2b1a04f93f8e701eced Ā· stratum-mining/sv2-spec Ā· GitHub :

The size of the search space for one Standard Job, given a fixed nTime field, is 2^(NONCE_BITS + BIP320_VERSION_ROLLING_BITS) = ~280Th , where NONCE_BITS = 32 and BIP320_VERSION_ROLLING_BITS = 16 . This is a guaranteed space before nTime rolling (or changing the Merkle Root by sending a new Job).

This nTime rolling could be limited to similar numbers. E.g. a hypothetical 3 peta hash beast would need to roll the timestamp by 10 seconds every second. If it gets a new template every 30 seconds, it would roll by 300 seconds. Beyond that a pool proxy could (and should) just roll the extranonce and feed the miner a new template more frequently.

Current proposal and implementation

The timewarp fix currently deployed on testnet4 also allows nTime to go backwards by 600 seconds.

Currently when Bitcoin Core proposes a new block template it will determine the timestamp as follows:

  1. Current time
  2. If the MTP rule requires it, bump time
  3. On testnet4, for the first block of a retarget period, if the previous block is from the future, bump time again, but minus 10 minutes.

See bitcoin/src/node/miner.cpp at 733fa0b0a140fc1e40c644a29953db090baa2890 Ā· bitcoin/bitcoin Ā· GitHub

The problem

The 600 second grace period is cutting it too close imo, and we should consider increasing it to 2 hours for any future proposal (see Zawyā€™s Alternating Timestamp Attack and https://delvingbitcoin.org/t/great-consensus-cleanup-revival/).

For the discussion here Iā€™ll assume that the pool software[0] and miner firmware doesnā€™t ignore the template timestamp.

Now if a malicious miners set their timestamp 2 hours in the future, relative to our node clock, and if our template is used by an ASIC that wants to roll nTime forward by up to 600 seconds, this is only safe if we assume all our peers have a matching clock. But that defeats the purpose of the 2 hour future rule: we shouldnā€™t assume nodes have accurate clocks.

We should also strongly discourage nTime rolling beyond a few minutes, so as to not eat too much into the network wide two hour tolerance for inaccurate clocks.

[0] public-pool (Ensure mintime is considered for jobs Ā· benjamin-wilson/public-pool@4282233 Ā· GitHub) and SRI (fix coming) ignored the template timestamp, which only became obvious on testnet4 because its timestamps are far in the future. But these are not production environments.

1 Like

The most strict MAX_TIMEWARP I remember was my 1-day recommendation (and on every block), if the MTP is more than that far in the past. I thought it was known that something small like 600 was dangerous. Off-hand I canā€™t remember the issue but I believe itā€™s because you want to be sure MTP is the strictest limit to assigning times in the past. Maybe it was just a compatibility issue such as software somewhere assuming MTP is always the limit. Limiting it only on difficulty adjustment is even more of a red flag since itā€™s more strict than the MTP. Block at height H has MTP as the limit, then H+1 has -600, then H+2 is back to MTP ā€¦ seems like someone will find a way to break something.

If a miner has that much hashrate, his 1st timestamp should be in the past at 1/2 his average forward seconds of rolling, if MTP allows. Either way, having a known procedure discloses his hashrate if itā€™s not already known, or discloses who won if hashrates are known.

A miner wonā€™t roll past the new future time limit if the prior block was at the limit (and for some reason he uses that as his starting timestamp), unless his hashrate is more than the network hashrate. Time advances faster than his rolling, so anyone accepting the prior block would accept his.

The original consensus cleanup proposal uses 600 seconds for the reasons I describe above.

The testnet4 implementation started out with 7200 seconds. This was to allow miners to use the system clock. But that was considered a bad idea and so it was reverted back to 600 here: Move maximum timewarp attack threshold back to 600s from 7200s by TheBlueMatt Ā· Pull Request #30647 Ā· bitcoin/bitcoin Ā· GitHub

And so Iā€™m proposing to bring it back to 7200, but not for the reason testnet4 initially did it.

As you say, anything less than 1 day is probably good enough.

Seperate from that is your observation here: Zawyā€™s Alternating Timestamp Attack

I didnā€™t see any reasoning in your OP that led me to think it should me more restrictive than the MTP. I donā€™t understand @TheBlueMattā€™s comment that 7200 reverse would allow slightly more inflation than 600. I prefer @murchā€™s recommended 2 weeks limit over 2 hours. I meant anything more than a day is good and safe and anything less is risky.

Correct, I was worried about the value being too low. I have no opinion on whether it could be (much) bigger.

Then 7200 is too low because it could be more restrictive than the MTP, and usually would be is if itā€™s trying to correct a +7200 advance in time.

Hmm i agree that we should seek to minimize the potential for creating an (even temporarily) invalid block. But thatā€™s a lot of ifā€™s to get there:

  • A miner is malicious; and
  • This miner finds the last block in a period; and
  • Our ASIC rolls nTime, we find the first block of the new period; and
  • Our ASIC finds a solution with nTime + n; and
  • We find the block in less than s seconds; and
  • A majority of the hashrate has a clock behind ours by more than n + s - 600 seconds.

I strongly disagree. The StratumV2 spec explicitly (I thought?) only allows a miner to roll nTime once per second. In Sv2, machines with more than 280 TH/s can either request multiple jobs from the pool by using multiple ā€œchannelsā€, or can build their own work by requesting a merkle path to the coinbase where they can roll the extranonce. Iā€™m unaware of any existing miners that roll nTime much more aggressively than one per second, but the existence of pool software that hard rejects rolling past 600 seconds strongly suggests it didnā€™t exist at the time that document was written.

One of the reasons for making testnet4 limit at 600 seconds is to create a testing ground and make sure that no miners are broken by the limitation, giving us more data to make sure 600s is safe (though we believe it is for the above reasons).

I further donā€™t buy that we need to make inferior protocol design decisions on the basis of some theoretical future mining device which ignores protocol restrictions on nTime rolling both in Sv2 and implicit in the Bitcoin protocol.

1 Like

Provided we donā€™t roll nTime by more than whatever the value is that we can push nTime backwards (600 seconds in both cases here) towards real time, this doesnā€™t seem like much of a concern? If our blockā€™s timestamp is invalid, then the malicious blockā€™s timestamp will also have been to be too far in the future, so any node that would have rejected our block due to the timestamp would also reject it due to its parent, so no matter what timestamp we gave it our block would be rejectedā€¦

Redoing the math: nNonce rolling gives 4GH; nNonce+BIP320 gives 280TH; if you bump nTime once per second (as expected) thatā€™s 4GH/s or 280TH/s. If you want to get into the PH/s range (seems like the best antminer currently advertised is in the 0.5PH/s range), then assuming you only provide new work every 30 seconds, then you probably want 7 bits of nTime to roll (128 seconds), which gets you 1.2PH/s. If you need the final bits of nTime to be zeroed to roll them, then the total offset is roughly doubled (+0 to +254 seconds, about 4 minutes). If youā€™ve got a range of 600s, then you can roll 8 bits of nTime for 2.4PH/s; if you also provide new work every 10s, then youā€™ve got 7.2PH/s; if you provide 4 units of work every 10s, thatā€™s ~30PH/s. So afaics 600s should be pretty fine, though I donā€™t have any objection to increasing it.

Weā€™re considering here a rule that the first block of a new periodā€™s timestamp has to be bounded below by both mediantime and prev->nTime-K. A different way to achieve the same goal would be require that the last block of a periodā€™s timestamp has to be bounded below by mediantime, but also bounded above by mediantime+K. K here should perhaps be something on the order of 3 hours (one hour because mediantime already lags wall-clock time, and then another 2 hours on top of that to ensure thereā€™s some room for rolling, slow blocks, etc).

The downside of such an approach is that existing mining software that simply continually bumps nTime (once per second, eg) will eventually exceed the upper bound, and produce invalid blocks.

Sorry if that was a bit stream of consciousness.

1 Like

If thatā€™s the intention, then the spec should be clarified. Currently thereā€™s only an ambiguous statement buried in the discussion section. See sv2-spec/10-Discussion.md at 52e1fa22f68c343a3d25a2b1a04f93f8e701eced Ā· stratum-mining/sv2-spec Ā· GitHub and the header-only-mining (HOM) discussion here: cleanup and update Mining Protocol specs by plebhash Ā· Pull Request #98 Ā· stratum-mining/sv2-spec Ā· GitHub

make sure that no miners are broken by the limitation,

Unfortunately this bug could exist for years without detection, only revealing itself in the distant future when chips are fast enough to cause a problem.

If the sv2 spec explicitly disallows accelerated nTime rolling then indeed the attacker would take the same risk as their victim.

But otherwise the malicious miner could use extra_nonce rolling to give themselves a bigger safety margin than their accelerated nTime rolling competitor.

Iā€™m not convinced (yet) that we need to make these numbers so tight. It seems that having a few hours of padding, instead of 10 minutes, avoids some actual bugs (pool software ignoring nTime) and theoretical future bugs. While the only downside is a minuscule increase in worst case inflation.

So what is the attack scenario here? Minority miner A tries to get miner B to waste its hashrate by producing an invalid block. Miner A somehow figures out miner B is using nTime rolling past 600 seconds and its local clock is ahead of everyone elseā€™s. Miner A mines a block such as it is invalid to everyone else but miner B, in the hope that miner B would start mining with nTime_A - 600, roll the timestamp past nTime_A, and find a block with nTime_B = nTime_A + s in less than s seconds such as Aā€™s block is valid to the rest of the network but Bā€™s block is not. And all that before the rest of the network found a different pair of block.

At this point this is not an attack, itā€™s a footgun. I donā€™t see how A could ever expect to gain anything from trying this.

I donā€™t think itā€™s a fair characterization of the downside. Faster subsidy emission is only one of the harms of an artificially increased block rate. And if the leeway is large enough, the block rate increase isnā€™t minuscule anymore. Here is some numbers:

Leeway: 10 minutes. Max diff decrease per period: 1.0004960317460319. Number of periods to take the diff to 1 is 65169.20533651417, to halve the diff is 1397.7312609540088 and to reduce it by 10% is 212.45947546985983.
Leeway: 60 minutes. Max diff decrease per period: 1.0029761904761905. Number of periods to take the diff to 1 is 10874.99226688242, to halve the diff is 233.24385460225878 and to reduce it by 10% is 35.453787426590814.
Leeway: 120 minutes. Max diff decrease per period: 1.005952380952381. Number of periods to take the diff to 1 is 5445.563646962276, to halve the diff is 116.79495712078635 and to reduce it by 10% is 17.753194781141502.
Leeway: 240 minutes. Max diff decrease per period: 1.0119047619047619. Number of periods to take the diff to 1 is 2730.8374380149667, to halve the diff is 58.57025317383194 and to reduce it by 10% is 8.902859666282216.
Code
import math

CURRENT_DIFFICULTY = 108522647629298

for leeway in [10, 60, 2*60, 4*60]:
    max_rate_decrease = 1 + leeway/20160
    diff_1 = math.log(CURRENT_DIFFICULTY, max_rate_decrease)
    diff_half = math.log(2, max_rate_decrease)
    diff_ninety = math.log(1/0.9, max_rate_decrease)
    print(f"Leeway: {leeway} minutes. Max diff decrease per period: {max_rate_decrease}. Number of periods to take the diff to 1 is {diff_1}, to halve the diff is {diff_half} and to reduce it by 10% is {diff_ninety}.")

I agree this is pretty unlikely.

This doesnā€™t seem that big a deal either. Assuming this gets deployed by the next halving, it 10% is only 0.15 BTC.

Maybe 150 minutes is a good enough number? Thereā€™s no circumstance I can (currently) think of where that can introduce a bug. While at the same time it takes over 10 retarget periods to reduce difficulty by 10%.

There is plenty of good enough numbers. Unless we have a good reason not to iā€™d say letā€™s stick to the already proposed value of 600 seconds. Iā€™ll email the mining dev mailing list to ask if there is anything weā€™re overlooking here.

I would turn that round. We shouldnā€™t introduce a soft fork rule that can be broken by accident, unless thereā€™s a really good reason to. The original motivation for the timewarp attack was to prevent a very destructive attack that could happen in a matter of weeks. That doesnā€™t require a 600 second limit.

If the timewarp attack had been fixed with a 24 hour limit, I donā€™t think anyone would have proposed a subsequent soft fork to lower it to 600 seconds.

Above I linked to two actual mining pool software bugs that caused invalid blocks on testnet4 because they were using the system clock instead of the block template nTime. Bitcoin Core itself (briefly) had a bug where it could accidentally violate the timewarp rule. So thatā€™s three real bugs found with minimal testing. We have no idea how much (closed source, unmaintained) mining software is out there, and we canā€™t expect every pool, individual mining farm and solo operator to thoroughly test their entire infrastructure on testnet4.

Most modern soft forks have used standardness to protect miners against accidentally producing an invalid block if they donā€™t upgrade their node. They also need to upgrade their node to not accidentally mine on top of an invalid block. And as a community we rely on a very large majority to upgrade their node in order to safely deploy the soft fork network wide.

The timewarp fix with a 600 second limit deviates from that because it requires miners to also check their entire stack of mining software. Whereas a 150 minute threshold only requires them to upgrade their node.

Miners should of course, regardless of the soft fork, check their mining software for the above bugs (they only became visible because on testnet4 the 20-minute-difficulty-1 rule is exploited more aggressively than on testnet3, pushing MTP structurally past wall clock time). But thatā€™s an orthogonal issue.

I aree with everything @sjors has said.

I donā€™t see any reason 600 should be used.

The onus shouldnā€™t be to prove thereā€™s a danger. We know thereā€™s a danger to more restrictions breaking something.

I donā€™t understand why 600 was proposed. How is being more restrictive beneficial?

Bitcoin should be like a rock upon which developers can build without getting rug-pulled like a Facebook API. Microsoft build its profitable evils on the back of a similar rock: every developerā€™s program since 1981 still runs on it.

If you break someoneā€™s software, you destroy far more systemic bitcoin value due to perception than the harm to the victims.

I regret giving walls of text on theoretical ideals if it distracted from @murch 's wise recommended fix that is the least restrictive and therefor safest. Itā€™s not even the same type of change as this one which has to specify a somewhat arbitrary number.

In the distant past nullcā€™s (even wiser?) recommendation on timewarp was ā€œdonā€™t touch itā€. His argument was that it can be easily fixed if it happens and thereā€™s already a non-fixable fundamental break in the security (>50%) if it does. On testnet itā€™s more of a risk that needs addressing. With a some imagination, leaving it alone on main chain could be a honey pot you want to keep.

This is a truism. I donā€™t think itā€™s necessary to say i agree.

Again you only give one of the multiple motivations for fixing timewarp. I already called you up on that in a previous post. As iā€™ve described in my writeup as well as in private discussions i donā€™t think it is the most likely scenario. Itā€™d make more sense for miners to artificially increase the block rate thereby bringing available block space up and, all other things equal, fee rates down. With the current concentration of mining we shouldnā€™t underestimate the odds of it happening. We should not keep in place the incentive for them to pull it off by making the fix too loose.

Thatā€™s plausible because a soft fork is risky and expensive. Not because somehow 24 hours is a better value than 10 minutes.

Those softwares are already broken today due to the MTP rule. ā€œExisting broken software may produce invalid blocks under exceptional circumstancesā€ is not a valid argument for looser bounds if said software can already produce invalid blocks with current rules under exceptional circumstances!

What is this bug?

I agree, but it is not on itself an argument for a looser value. Assuming you are still proposing to use 150 minutes instead, could you provide an argument for why this value would possibly better accommodate broken software we do not know about?

How so? Could you be more specific? I donā€™t think weā€™ve established that.

In conclusion, let me state i do not have a too strong opinion in favour of a 600 seconds grace period. It just makes sense to me and i donā€™t think we should change it unless there is a good reason to.

There is good reasons for the fix to be neither too loose nor too tight. 600 seconds is a good sweet spot as it seems to get rid of the incentive for miners to artificially increase the block rate (with a 600 seconds grace period a 10% block rate increase would take 212 periods) while being loose enough that using higher values wouldnā€™t materially decrease the probability of creating an invalid block. You have dismissed the reasons for avoiding a looser fix without providing compelling arguments why going for a 150 minutes grace period would lower risks.

They canā€™t increase the block rate to get 10% more unless they also have > 50% and privately mine for 212 periods to reliably keep the MTP held back. Publicly theyā€™d need something like 99%. But if they do a private mine they get 50% more blocks per time than they would publicly after the 1st adjustment for as long as they mine.

But they donā€™t get the 10% gain for the same reason. Delaying 600 s in 1st cycle lowers the difficulty enough for him to get an extra block in the 2nd. But getting 1 more block means difficulty goes up in the 3rd cycle to offset the gain if he doesnā€™t do the -600 in the next cycle. So he can only maintain getting 1 extra block per cycle. Itā€™s not an advantage that can accumulate. So he gets only 212 blocks which isnt important compared to getting 212 Ɨ 2016 excess blocks from doing the private mine (over a public mine). So itā€™s a 0.05% gain over a private mine and a -3 hour limit is a 0.9% gain.

  • cycle => blocks/cycle at 50% => blocks per 2 weeks
  • 1 => 1008 (normal before attack) => 1008
  • 2 => 2016 (private mine begin -3 hr timestamp) => 1008 (takes 4 weeks)
  • 3=> 2016 (-3 hr) => took 2 weeks minus 3 hr due to previous stamp
  • 4 => 2016 (-3 hr) => took 2 weeks minus 3 hr

He keeps the last timestamp in a cycle at current time. Thereā€™s not a way to hold it back or push it forward to help. The only thing he gets over a private mine is more time at the end to mine 1 extra week than he otherwise could have. After 112 cycles he would get a gain of 2 weeks to get 2016 +18 blocks more blocks than doing nothing, less than 2% of the ā€œexcessā€ gains of just being a private mine ( 2016/2 * 112).

I see no reason to dismiss the concerns that -600 will break something. This is my 3rd request that someone explain how it helps.

I donā€™t think this is dangerous for the network for the numbers weā€™re discussing, i.e. a 10% speedup after a sustained 51% attack for 59 difficulty periods. And why would a miner want to reduce their own fee revenue?

But no miner ran into that bug so far, because it takes significant hash rate to push MTP above wall clock time and perform this attack. With the timewarp rule, griefing a miner that uses wall clock time only requires finding the last block in a retarget period and setting its time (a bit more than) 600 seconds in the future. Both attacks are free, but the latter only requires luck, no large percentage of hash power.

The main change adds this to miner.cpp:

if (consensusParams.enforce_BIP94) {
        // Height of block to be mined.
        const int height{pindexPrev->nHeight + 1};
        if (height % consensusParams.DifficultyAdjustmentInterval() == 0) {
            nNewTime = std::max<int64_t>(nNewTime, pindexPrev->GetBlockTime() - MAX_TIMEWARP);
        }
    }

The pull request description is focussed on a different scenario, but this (also) fixes the second griefing attack.

Yes, 150 minutes makes the above griefing attack on broken miner software impossible, because the attacker canā€™t put their timestamp more than 120 minutes in the future.

Hopefully the above example illustrates this? With a 150 minute threshold thereā€™s no (additional) danger in having a bug in mining software that ignores the nTime value provided in the Bicoin Core template. They might ignore it by accident like SRI Pool and JDC update `nTime` field with current timestamp - they should use the one sent by TP Ā· Issue #1324 Ā· stratum-mining/stratum Ā· GitHub, or they might have custom code that calculates nTime correctly under the current rules (but not accounting for the new timewarp rule).

While trying to illustrate the griefing attack with a functional test, I found another bug :slight_smile: Will link to a fix PR here.

Update:

Aside from the fix it illustrates the griefing attack above.

Miners care about the whole block reward, not only transaction fees. A higher block rate means they can claim more of the subsidy, and possibly more fees. Note also i said it would bring (all others things equal) fee rates down, i did not say anything about total fee revenues.

I agree it would make it much less likely than with todayā€™s incentives, but i wouldnā€™t be as confident as to rule it out entirely. Mining today is extremely concentrated, and the most part of minersā€™ revenues (block subsidy) is decreasing exponentially. Of course it would not be marketed as a 51% attack, how about a ā€œminer activated fee rate easingā€ whereby miners get more revenues and users get lower fees? Note also itā€™s not strictly necessary to reorg out the blocks of honest miners to exploit this, especially with a large share of the network hashrate participating.