← index

Timewarp attack 600 second grace period

An archive of delvingbitcoin.org · view original topic →

Sjors Provoost · #1 ·

Background

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

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.

Zawy · #2 ·

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.

Sjors Provoost · #3 · · in reply to #2

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

Zawy · #4 · · in reply to #3

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.

Sjors Provoost · #5 · · in reply to #4

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

Zawy · #6 · · in reply to #5

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.

Antoine Poinsot · #7 ·

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:

Matt Corallo · #8 ·

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.

Anthony Towns · #9 · · in reply to #7

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.

Sjors Provoost · #10 ·

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.

Antoine Poinsot · #11 · · in reply to #9

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: EDIT: the numbers are incorrect as they assume gains accumulate across periods, which isn’t the case once the timestamp of the first block needs to move forward.

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}.")

Sjors Provoost · #12 ·

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%.

Antoine Poinsot · #13 · · in reply to #12

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.

Sjors Provoost · #14 · · in reply to #13

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.

Zawy · #15 · · in reply to #14

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.

Antoine Poinsot · #16 · · in reply to #14

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.

Zawy · #17 · · in reply to #16

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.

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.

Sjors Provoost · #18 ·

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).

Sjors Provoost · #19 ·

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.

Antoine Poinsot · #20 · · in reply to #18

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.

Sjors Provoost · #21 ·

@AntoineP exponential growth is always a problem if it goes on for long enough. Is there some upper bound to the speedup or can blocks come in every second if the 51% attack persists?

Antoine Poinsot · #22 · · in reply to #18

Wait, this is not a “bug in Bitcoin Core” this is a fix to the PR which changed block validation rules without adapting the block creation logic…

I’m not sure what exponential growth you are referring to. Are you asking if in theory it’s possible to extremely increase the block rate with any grace period value if the attack persists long enough? If so then yes.

In theory the difficulty can always be brought down to 1 if you can constantly claim what took you 2 weeks took you 2 weeks + x seconds. See the numbers i shared above. But i’m not sure how relevant it is: for instance with a 600 seconds grace period it would take hundreds of years to bring the difficulty down to 1.

Sjors Provoost · #23 ·

Which is a bug in Bitcoin Core. It was caught before release. The second bug wasn’t, it’s fixed by 31600. It just illustrates how easy it is to screw things up when the grace period is only 600 seconds.

Update: it was a known omission before the original PR was merged, so maybe shouldn’t count as a bug: Move maximum timewarp attack threshold back to 600s from 7200s by TheBlueMatt · Pull Request #30647 · bitcoin/bitcoin · GitHub

Exactly. I’m not really worried about a one-off 10% increase in block speed. But compounded over time it’s more worrying.

Hundreds of years is enough time to come up with a plan, but serious problems could happen long before difficulty reaches 1. E.g. deep reorgs would be easier to execute.

If we take a difficulty reduction of 10x as the (arbitrary) definition of “bad”, and if assume a 10% difficulty decrease every ~10 retarget periods, it takes 240 retarget periods to reduce to reach this “bad” situation. That’s several years, probably enough to discuss and deploy a soft-fork to make it stricter. But not super comfortable either.

Whereas with a grace period of 600 reaching “bad” takes 20 times longer, which seems very safe.

So this may be a good argument for keeping the number at 600 and to insist that pools either use the min_time field provided by Bitcoin Core, or implement their own code for the minimum time that takes both MTP and the new timewarp rule into account.

Zawy · #24 · · in reply to #22

Not if you want to win a 51% race. After claiming you took 2 wks + x, you’re going to solve the next 2016 blocks in 2 wks - x if you maintain your hashrate. If you don’t, you’re not going to win chain work. To claim it took 2 wks + x in that round, you have to add 2x to to your 2 wks - x real time. You have only 1 x to manipulate without penalty and the other x you have to add to real time. As those accumulate past 7200 seconds, no nodes will accept your blocks.

Antoine Poinsot · #25 · · in reply to #24

Yes, that’s why i started with “in theory”. In practice i think any of the discussed values for the grace period make extreme drops in difficulty completely unlikely.

Zawy · #26 · · in reply to #25

There’s no theory that allows an increase the block production rate. A difficulty drop won’t accumulate more than 1 round unless hashrate drops. You want it to drop if that happens. Difficulty can’t drop more 1.2% in 1 and only 1 round if the reverse limit is 7200 and the attacker sets the last block to 7200 in the future. There’s no average decrease in difficulty because it will cause an increase in difficulty in the next round.

Sjors Provoost · #27 · · in reply to #26

If the difficulty drop doesn’t accumulate then I flip back to my preference for a grace period high enough to avoid bugs, and which maintains the rule that the minimum nTime of a block is given by MTP + 1, without the need for a clock.

Update 2025-01-09: you don’t need a clock to honor the timewarp rule.

Pieter Wuille · #28 ·

@zawy Imagine the proposed rule, that the first block in a period (i.e., one where n = 0 \mod 2016) must have a timestamp no more than 600s before its immediate predecessor (the last block of the previous period).

Call this constant G = 600 (grace period). Also introduce P = 2016 \cdot 600 (the period, 2 weeks).

For simplicity, let’s replace the “timestamp must be strictly larger than MTP” rule with a “timestamp must not be below MTP” rule (i.e., we allow timestamp=MTP). This doesn’t materially affect the attack, but means we don’t need to increment by 1s every 6 blocks.

The following sequence of timestamps for periods is valid, starting at time 0:

Within each period, the difference between the first and last block timestamp is exactly P, so the difficulty will not adjust. However, the net timestamp increase per period is only P-G, i.e., 10 minutes less than 2 weeks.

So this allows attacker to permanently keep the block rate higher than intended, without incurring a difficulty increase cost.

The reason for the 600s grace period is that is compensates the effect of the off-by-one in the difficulty calculation (it only looks at how long 2015 blocks take, not 2016), which would otherwise result in (under constant hashrate) periods of 2 weeks plus 10 minutes. So with the proposed rule, a timewarp attacker can not cause a steady constant-difficulty block production rate of more than 2016 per two weeks.

There is another effect due to asymmetry of the Erlang distribution that results in another 10 minutes lengthening under constant hashrate. I forget why this isn’t incorporated for compensation in the proposed GCC rule.

EDIT: ah, it’s explained in footnote [1] in bips/bip-XXXX.mediawiki at 7f9670b643b7c943a0cc6d2197d3eabe661050c2 · TheBlueMatt/bips · GitHub. The attacker can ignore the first effect but not the second.

Zawy · #29 · · in reply to #28

I don’t understand how one of the effects can be ignored. Reference [1] switched from saying we’re looking at the Erlang of 2015 blocks to 2016 blocks. It seems to imply the attacker received a double benefit, i.e. one by increasing timespan by 600, but then another by somehow going back in real-world time by 600 seconds to get 2016 * 600 seconds to mine instead of being stuck with 2015 * 600.

[ for time taken to mine the last 2015 blocks ] … IBT is 2016/2014 * 600. This is equivalent to 600 * E(2016 * 600/X) where X~ErlangDistribution(k=2015, λ=1/600). In the case of a miner deliberately reducing timestamps by 600 seconds on the difficulty-retargeting block, we are effectively changing the difficulty multiplier to (2016 / (time taken to mine the last 2016 blocks + 600)), or 600 * E(2016 * 600/(X + 600)) where X~Erlang Distribution(k=2016, λ=1/600), which is effectively targeting an inter-block time of ~599.9999 seconds.

Concerning if this means “difficulty drop can accumulate”, yes, for the last block in this sequence he can use real time in for the last timestamp, then the next period will have a lower difficulty, but then the next period would be back to normal. He would have to do 168 periods with a 2 hr limit to drop difficulty to I think 1/2.

I think there’s a lot more risk to making it strict. I can see only the very smallest of benefit in making it strict, but I can’t estimate the risk that’s being increased.

Pieter Wuille · #30 · · in reply to #29

Just so we’re talking about the same thing, my points are:

Zawy · #31 · · in reply to #30

Rather than 2017 / 2018, we’re measuring a timespan across 2015 blocks, so via Erlang we expected 2014 blocks. Therefore the adjustment is supposed to be “2014_blocks / 2015_expected_solvetimes”. But the code calculates “2016_blocks / 2015_times”. Allowing +600 in the denominator makes it 2016 / 2016. But we should add 1 to the numerator if we’re going to add one to the denominator: (2014+1) / (2015 + 1) = 2015 / 2016. So 2016 / 2016 is too large.

For your example of subtracting 600 at the beginning and end, the difficulty is too much as always 2016 / 2015 instead of “staying constant” with 2014 / 2015, i.e. it’s taking 600.6 seconds / block (2 weeks + 20 minutes). So G = 3 * 600 in order to solve in 2 weeks minus 10 minutes?

Pieter Wuille · #32 · · in reply to #31

Let’s say c_i is the actual time at which block i is found, and t_i is its timestamp.

If the attacker (who is assumed to have 100% hashrate) follows this strategy:

Then the observed duration of the 2015 blocks in period p (relevant for difficulty adjustment) is t_{2016p+2015} - t_{2016p} = c_{2016p+2015} - c_{2016p-1} + G, i.e. the time it took to mine 2016 blocks plus G.

The difficulty multiplier m will thus be m = \frac{P}{X + G}, where X is Erlang distributed with k=2016 and \lambda=\operatorname{hashrate} \cdot \frac{\operatorname{target}}{2^{256}}.

In a simulation with G=600 I get an effective average block interval under constant hashrate of 599.9997… seconds. With G=7200 I get 596.7211… seconds.

Zawy · #33 · · in reply to #32

OK, now I see. It’s not that an adjustment for both Erlang and “2015 hole” aren’t needed, it’s that 600 seconds before the previous block isn’t a 600 second lie, it’s a 1200 second lie because we expected a timestamp 600 seconds after it.

Pieter Wuille · #34 · · in reply to #33

That’s a great way to put it.

Sjors Provoost · #35 ·

See this comment: Great Consensus Cleanup Revival - #66 by AntoineP

Although @AntoineP now agrees with increasing the grace period, for reasons explained in the above comment, it might still be useful to investigate the following:

Does anyone have a log of when their node saw blocks arrive over e.g. the past 10 years? Perhaps we can determine, or rule out, the presence of such a bug in the wild.

I don’t know how though.

One problem with such analysis is that any block violating the MTP rule would not get propagated, so you wouldn’t observe it. Even compact block relay checks the header: bips/bip-0152.mediawiki at 5333e5e9514aa9f92810cfbde830da79c44051bf · bitcoin/bips · GitHub

Another problem is that you don’t know know the clock of the (pool) node that generated the block template.

A bug that ignores the template timestamp can be present in the pool software, the ASIC (stock or alternative) firmware or a proxy run by the miner.

If the bug is in pool software, we might be able to observe it live by studying live stratum jobs. This is easy on testnet4 today because MTP is ahead of wall clock time there. But not on mainnet.

Perhaps long term logs of such jobs exist, though again I’m not sure what to look for.

It seems a bit less likely to me that ASIC firmware ignores the timestamp provided in its stratum job. My own experience with old linux machines is that NTP doesn’t always work out of the box, so someone would have noticed such a bug the hard way quickly.

I have no idea how stratum proxies work. Those might exist at the scale of a small mining farm that rarely finds an actual block, so it seems undetectable.

Zawy · #36 · · in reply to #35

Systemic use of NTP breaks security.

Sjors Provoost · #37 ·

I’m still trying to understand whether the difficulty decrease compounds. Take the following (same MTP simplification as above):

So each period is stretched by G, causing the difficulty to drop every time. For G = 2 hours, this would be 0.6% per retarget period. That translates to about 15% per year.


Note that any difficulty decrease accumulation can be reset by an honest miner finding the last block of any retarget period.

Pieter Wuille · #38 · · in reply to #37

That’s fair, I think this may work, but even if it does, I don’t believe this is a problem.

Given that the end-of-window time (which is bounded by the current time) goes up by P per block, it means the real-time block production rate in your scheme is exactly P per window, exactly the intended rate. So, from the perspective of validating nodes, there is no resource consumption attack.

And beyond that, indeed, this allows miners to reduce the difficulty while keeping the block rate constant. But a colluding group of miners is always able to do that: they can just agree to all reduce their hashrate, for any difficulty reduction they’d like. But, all that achieves is making it easier/cheaper for a competing miner who doesn’t follow the scheme to take over.

Antoine Poinsot · #39 · · in reply to #37

What Pieter said. As soon as you have a restriction on the timestamp of the first block in a period, miners can only “carry over” a fixed block rate increase. There is no compounding.

Pieter Wuille · #40 · · in reply to #39

Right. Exactly setting the timestamps isn’t something a computationally-bounded miner can do because it is still a Poisson process.

If you try to adapt Sjors’ scheme to a real algorithm for deciding timestamps, I think you exactly get the scheme I posted above. And we simulated that: it has no compounding effect because the attackers’ block production rate is affected by the difficulty adjustment they caused, providing negative feedback. But, again, even if there was a compounding effect it would not be a concern, as mentioned above, because it’s at worst equivalent to attackers just choosing to reduce their hashrate.

Jonny1000 · #41 · · in reply to #39

Thanks Antoine and Pieter

I am just trying to wrap my head around why the 0.6% cannot compound.

If miners conduct the timewarp attack, then in epoch 2 they manipulate the difficulty down 0.6%

Then in epoch 3, since the difficulty is lower (But the hashrate is the same), the miners are expected to produce 0.6% more blocks. Then they use the fake timestamp trick again, shifting the difficulty down 0.6%, but that only offsets the impact of the 0.6% more blocks. So with this 2 hour rule, miners can only achieve a one-off 0.6% downward shift?

Am I thinking about that correctly?

Miners could of course just produce fewer blocks and keep shifting the difficulty downwards, but then that isnt really an attack with fake timestamps, that is just mining less

Pieter Wuille · #42 · · in reply to #41

What practically goes wrong if you try to pull of the scheme exactly as described by Sjors, is the following, I think.

As an attacker you have a certain hashrate, so you will find blocks sometimes faster, sometimes slower. From time to time, you will find a block 2016k at time before (1+k)P. You can now do three things:

Sjors Provoost · #43 ·

Ah, it seems I was confused indeed.

What I had in mind is that block timestamps would gradually drift further into the past, because blocks are coming in faster. So the attacker won’t use the real timestamp, won’t wait and doesn’t mine in secret.

But actually when blocks come in fast kP] is in the future. So my scheme runs into the rule that blocks can’t be more than 2 hours from the future.