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.

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