@keep-network/tbtc-v2 0.1.1-dev.107 → 0.1.1-dev.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/artifacts/Bank.json +3 -3
  2. package/artifacts/Bridge.json +5 -5
  3. package/artifacts/BridgeGovernance.json +3 -3
  4. package/artifacts/BridgeGovernanceParameters.json +2 -2
  5. package/artifacts/Deposit.json +2 -2
  6. package/artifacts/DepositSweep.json +2 -2
  7. package/artifacts/EcdsaDkgValidator.json +1 -1
  8. package/artifacts/EcdsaInactivity.json +1 -1
  9. package/artifacts/EcdsaSortitionPool.json +2 -2
  10. package/artifacts/Fraud.json +2 -2
  11. package/artifacts/KeepRegistry.json +1 -1
  12. package/artifacts/KeepStake.json +2 -2
  13. package/artifacts/KeepToken.json +2 -2
  14. package/artifacts/KeepTokenStaking.json +1 -1
  15. package/artifacts/MaintainerProxy.json +3 -3
  16. package/artifacts/MovingFunds.json +2 -2
  17. package/artifacts/NuCypherStakingEscrow.json +1 -1
  18. package/artifacts/NuCypherToken.json +2 -2
  19. package/artifacts/RandomBeaconStub.json +1 -1
  20. package/artifacts/Redemption.json +2 -2
  21. package/artifacts/ReimbursementPool.json +2 -2
  22. package/artifacts/Relay.json +4 -4
  23. package/artifacts/T.json +2 -2
  24. package/artifacts/TBTC.json +3 -3
  25. package/artifacts/TBTCToken.json +3 -3
  26. package/artifacts/TBTCVault.json +9 -9
  27. package/artifacts/TokenStaking.json +1 -1
  28. package/artifacts/TokenholderGovernor.json +9 -9
  29. package/artifacts/TokenholderTimelock.json +8 -8
  30. package/artifacts/VendingMachine.json +3 -3
  31. package/artifacts/VendingMachineKeep.json +1 -1
  32. package/artifacts/VendingMachineNuCypher.json +1 -1
  33. package/artifacts/WalletRegistry.json +5 -5
  34. package/artifacts/WalletRegistryGovernance.json +2 -2
  35. package/artifacts/Wallets.json +2 -2
  36. package/artifacts/solcInputs/{2463cada7efb9c191b0fd4a22734bbd0.json → ca06d7336565753de6867fd69d3e145d.json} +12 -6
  37. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  38. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  39. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  40. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  41. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  42. package/build/contracts/bridge/BridgeGovernance.sol/BridgeGovernance.dbg.json +1 -1
  43. package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
  44. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  45. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  46. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  47. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  48. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  49. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  50. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  51. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  52. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  53. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  54. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  55. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  56. package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
  57. package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +4 -0
  58. package/build/contracts/relay/LightRelay.sol/ILightRelay.json +214 -0
  59. package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +4 -0
  60. package/build/contracts/relay/LightRelay.sol/LightRelay.json +443 -0
  61. package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +4 -0
  62. package/build/contracts/relay/LightRelay.sol/RelayUtils.json +10 -0
  63. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  64. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  65. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  66. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  67. package/contracts/relay/LightRelay.sol +606 -0
  68. package/package.json +1 -1
@@ -0,0 +1,606 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ // ██████████████ ▐████▌ ██████████████
4
+ // ██████████████ ▐████▌ ██████████████
5
+ // ▐████▌ ▐████▌
6
+ // ▐████▌ ▐████▌
7
+ // ██████████████ ▐████▌ ██████████████
8
+ // ██████████████ ▐████▌ ██████████████
9
+ // ▐████▌ ▐████▌
10
+ // ▐████▌ ▐████▌
11
+ // ▐████▌ ▐████▌
12
+ // ▐████▌ ▐████▌
13
+ // ▐████▌ ▐████▌
14
+ // ▐████▌ ▐████▌
15
+
16
+ pragma solidity ^0.8.9;
17
+
18
+ import "@openzeppelin/contracts/access/Ownable.sol";
19
+
20
+ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
21
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
22
+ import {ValidateSPV} from "@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol";
23
+
24
+ import "../bridge/IRelay.sol";
25
+
26
+ struct Epoch {
27
+ uint32 timestamp;
28
+ // By definition, bitcoin targets have at least 32 leading zero bits.
29
+ // Thus we can only store the bits that aren't guaranteed to be 0.
30
+ uint224 target;
31
+ }
32
+
33
+ interface ILightRelay is IRelay {
34
+ event Genesis(uint256 blockHeight);
35
+ event Retarget(uint256 oldDifficulty, uint256 newDifficulty);
36
+ event ProofLengthChanged(uint256 newLength);
37
+ event AuthorizationRequirementChanged(bool newStatus);
38
+ event SubmitterAuthorized(address submitter);
39
+ event SubmitterDeauthorized(address submitter);
40
+
41
+ function retarget(bytes memory headers) external;
42
+
43
+ function validateChain(bytes memory headers)
44
+ external
45
+ view
46
+ returns (uint256 startingHeaderTimestamp, uint256 headerCount);
47
+
48
+ function getBlockDifficulty(uint256 blockNumber)
49
+ external
50
+ view
51
+ returns (uint256);
52
+
53
+ function getEpochDifficulty(uint256 epochNumber)
54
+ external
55
+ view
56
+ returns (uint256);
57
+
58
+ function getRelayRange()
59
+ external
60
+ view
61
+ returns (uint256 relayGenesis, uint256 currentEpochEnd);
62
+ }
63
+
64
+ library RelayUtils {
65
+ using BytesLib for bytes;
66
+
67
+ /// @notice Extract the timestamp of the header at the given position.
68
+ /// @param headers Byte array containing the header of interest.
69
+ /// @param at The start of the header in the array.
70
+ /// @return The timestamp of the header.
71
+ /// @dev Assumes that the specified position contains a valid header.
72
+ /// Performs no validation whatsoever.
73
+ function extractTimestampAt(bytes memory headers, uint256 at)
74
+ internal
75
+ pure
76
+ returns (uint32)
77
+ {
78
+ return BTCUtils.reverseUint32(uint32(headers.slice4(68 + at)));
79
+ }
80
+ }
81
+
82
+ /// @dev THE RELAY MUST NOT BE USED BEFORE GENESIS AND AT LEAST ONE RETARGET.
83
+ contract LightRelay is Ownable, ILightRelay {
84
+ using BytesLib for bytes;
85
+ using BTCUtils for bytes;
86
+ using ValidateSPV for bytes;
87
+ using RelayUtils for bytes;
88
+
89
+ bool public ready;
90
+ // Whether the relay requires the address submitting a retarget to be
91
+ // authorised in advance by governance.
92
+ bool public authorizationRequired;
93
+ // Number of blocks required for each side of a retarget proof:
94
+ // a retarget must provide `proofLength` blocks before the retarget
95
+ // and `proofLength` blocks after it.
96
+ // Governable
97
+ // Should be set to a fairly high number (e.g. 20-50) in production.
98
+ uint64 public proofLength;
99
+ // The number of the first epoch recorded by the relay.
100
+ // This should equal the height of the block starting the genesis epoch,
101
+ // divided by 2016, but this is not enforced as the relay has no
102
+ // information about block numbers.
103
+ uint64 public genesisEpoch;
104
+ // The number of the latest epoch whose difficulty is proven to the relay.
105
+ // If the genesis epoch's number is set correctly, and retargets along the
106
+ // way have been legitimate, this equals the height of the block starting
107
+ // the most recent epoch, divided by 2016.
108
+ uint64 public currentEpoch;
109
+
110
+ uint256 internal currentEpochDifficulty;
111
+ uint256 internal prevEpochDifficulty;
112
+
113
+ // Each epoch from genesis to the current one, keyed by their numbers.
114
+ mapping(uint256 => Epoch) internal epochs;
115
+
116
+ mapping(address => bool) public isAuthorized;
117
+
118
+ modifier relayActive() {
119
+ require(ready, "Relay is not ready for use");
120
+ _;
121
+ }
122
+
123
+ /// @notice Establish a starting point for the relay by providing the
124
+ /// target, timestamp and blockheight of the first block of the relay
125
+ /// genesis epoch.
126
+ /// @param genesisHeader The first block header of the genesis epoch.
127
+ /// @param genesisHeight The block number of the first block of the epoch.
128
+ /// @param genesisProofLength The number of blocks required to accept a
129
+ /// proof.
130
+ /// @dev If the relay is used by querying the current and previous epoch
131
+ /// difficulty, at least one retarget needs to be provided after genesis;
132
+ /// otherwise the prevEpochDifficulty will be uninitialised and zero.
133
+ function genesis(
134
+ bytes calldata genesisHeader,
135
+ uint256 genesisHeight,
136
+ uint64 genesisProofLength
137
+ ) external onlyOwner {
138
+ require(!ready, "Genesis already performed");
139
+
140
+ require(genesisHeader.length == 80, "Invalid genesis header length");
141
+
142
+ require(
143
+ genesisHeight % 2016 == 0,
144
+ "Invalid height of relay genesis block"
145
+ );
146
+
147
+ require(genesisProofLength < 2016, "Proof length excessive");
148
+ require(genesisProofLength > 0, "Proof length may not be zero");
149
+
150
+ genesisEpoch = uint64(genesisHeight / 2016);
151
+ currentEpoch = genesisEpoch;
152
+ uint256 genesisTarget = genesisHeader.extractTarget();
153
+ uint256 genesisTimestamp = genesisHeader.extractTimestamp();
154
+ epochs[genesisEpoch] = Epoch(
155
+ uint32(genesisTimestamp),
156
+ uint224(genesisTarget)
157
+ );
158
+ proofLength = genesisProofLength;
159
+ currentEpochDifficulty = BTCUtils.calculateDifficulty(genesisTarget);
160
+ ready = true;
161
+
162
+ emit Genesis(genesisHeight);
163
+ }
164
+
165
+ /// @notice Set the number of blocks required to accept a header chain.
166
+ /// @param newLength The required number of blocks. Must be less than 2016.
167
+ /// @dev For production, a high number (e.g. 20-50) is recommended.
168
+ /// Small numbers are accepted but should only be used for testing.
169
+ function setProofLength(uint64 newLength) external relayActive onlyOwner {
170
+ require(newLength < 2016, "Proof length excessive");
171
+ require(newLength > 0, "Proof length may not be zero");
172
+ require(newLength != proofLength, "Proof length unchanged");
173
+ proofLength = newLength;
174
+ emit ProofLengthChanged(newLength);
175
+ }
176
+
177
+ /// @notice Set whether the relay requires retarget submitters to be
178
+ /// pre-authorised by governance.
179
+ /// @param status True if authorisation is to be required, false if not.
180
+ function setAuthorizationStatus(bool status) external onlyOwner {
181
+ authorizationRequired = status;
182
+ emit AuthorizationRequirementChanged(status);
183
+ }
184
+
185
+ /// @notice Authorise the given address to submit retarget proofs.
186
+ /// @param submitter The address to be authorised.
187
+ function authorize(address submitter) external onlyOwner {
188
+ isAuthorized[submitter] = true;
189
+ emit SubmitterAuthorized(submitter);
190
+ }
191
+
192
+ /// @notice Rescind the authorisation of the submitter to retarget.
193
+ /// @param submitter The address to be deauthorised.
194
+ function deauthorize(address submitter) external onlyOwner {
195
+ isAuthorized[submitter] = false;
196
+ emit SubmitterDeauthorized(submitter);
197
+ }
198
+
199
+ /// @notice Add a new epoch to the relay by providing a proof
200
+ /// of the difficulty before and after the retarget.
201
+ /// @param headers A chain of headers including the last X blocks before
202
+ /// the retarget, followed by the first X blocks after the retarget,
203
+ /// where X equals the current proof length.
204
+ /// @dev Checks that the first X blocks are valid in the most recent epoch,
205
+ /// that the difficulty of the new epoch is calculated correctly according
206
+ /// to the block timestamps, and that the next X blocks would be valid in
207
+ /// the new epoch.
208
+ /// We have no information of block heights, so we cannot enforce that
209
+ /// retargets only happen every 2016 blocks; instead, we assume that this
210
+ /// is the case if a valid proof of work is provided.
211
+ /// It is possible to cheat the relay by providing X blocks from earlier in
212
+ /// the most recent epoch, and then mining X new blocks after them.
213
+ /// However, each of these malicious blocks would have to be mined to a
214
+ /// higher difficulty than the legitimate ones.
215
+ /// Alternatively, if the retarget has not been performed yet, one could
216
+ /// first mine X blocks in the old difficulty with timestamps set far in
217
+ /// the future, and then another X blocks at a greatly reduced difficulty.
218
+ /// In either case, cheating the realy requires more work than mining X
219
+ /// legitimate blocks.
220
+ /// Only the most recent epoch is vulnerable to these attacks; once a
221
+ /// retarget has been proven to the relay, the epoch is immutable even if a
222
+ /// contradictory proof were to be presented later.
223
+ function retarget(bytes memory headers) external relayActive {
224
+ if (authorizationRequired) {
225
+ require(isAuthorized[msg.sender], "Submitter unauthorized");
226
+ }
227
+
228
+ require(
229
+ // Require proofLength headers on both sides of the retarget
230
+ headers.length == (proofLength * 2 * 80),
231
+ "Invalid header length"
232
+ );
233
+
234
+ Epoch storage latest = epochs[currentEpoch];
235
+
236
+ uint256 oldTarget = latest.target;
237
+
238
+ bytes32 previousHeaderDigest = bytes32(0);
239
+
240
+ // Validate old chain
241
+ for (uint256 i = 0; i < proofLength; i++) {
242
+ (
243
+ bytes32 currentDigest,
244
+ uint256 currentHeaderTarget
245
+ ) = validateHeader(headers, i * 80, previousHeaderDigest);
246
+
247
+ require(
248
+ currentHeaderTarget == oldTarget,
249
+ "Invalid target in pre-retarget headers"
250
+ );
251
+
252
+ previousHeaderDigest = currentDigest;
253
+ }
254
+
255
+ // get timestamp of retarget block
256
+ uint256 epochEndTimestamp = headers.extractTimestampAt(
257
+ (proofLength - 1) * 80
258
+ );
259
+
260
+ // An attacker could produce blocks with timestamps in the future,
261
+ // in an attempt to reduce the difficulty after the retarget
262
+ // to make mining the second part of the retarget proof easier.
263
+ // In particular, the attacker could reuse all but one block
264
+ // from the legitimate chain, and only mine the last block.
265
+ // To hinder this, require that the epoch end timestamp does not
266
+ // exceed the ethereum timestamp.
267
+ // NOTE: both are unix seconds, so this comparison should be valid.
268
+ require(
269
+ /* solhint-disable-next-line not-rely-on-time */
270
+ epochEndTimestamp < block.timestamp,
271
+ "Epoch cannot end in the future"
272
+ );
273
+
274
+ // Expected target is the full-length target
275
+ uint256 expectedTarget = BTCUtils.retargetAlgorithm(
276
+ oldTarget,
277
+ latest.timestamp,
278
+ epochEndTimestamp
279
+ );
280
+
281
+ // Mined target is the header-encoded target
282
+ uint256 minedTarget = 0;
283
+
284
+ uint256 epochStartTimestamp = headers.extractTimestampAt(
285
+ proofLength * 80
286
+ );
287
+
288
+ // validate new chain
289
+ for (uint256 j = proofLength; j < proofLength * 2; j++) {
290
+ (
291
+ bytes32 _currentDigest,
292
+ uint256 _currentHeaderTarget
293
+ ) = validateHeader(headers, j * 80, previousHeaderDigest);
294
+
295
+ if (minedTarget == 0) {
296
+ // The new target has not been set, so check its correctness
297
+ minedTarget = _currentHeaderTarget;
298
+ require(
299
+ // Although the target is a 256-bit number, there are only 32 bits of
300
+ // space in the Bitcoin header. Because of that, the version stored in
301
+ // the header is a less-precise representation of the actual target
302
+ // using base-256 version of scientific notation.
303
+ //
304
+ // The 256-bit unsigned integer returned from BTCUtils.retargetAlgorithm
305
+ // is the precise target value.
306
+ // The 256-bit unsigned integer returned from validateHeader is the less
307
+ // precise target value because it was read from 32 bits of space of
308
+ // Bitcoin block header.
309
+ //
310
+ // We can't compare the precise and less precise representations together
311
+ // so we first mask them to obtain the less precise version:
312
+ // (full & truncated) == truncated
313
+ _currentHeaderTarget ==
314
+ (expectedTarget & _currentHeaderTarget),
315
+ "Invalid target in new epoch"
316
+ );
317
+ } else {
318
+ // The new target has been set, so remaining targets should match.
319
+ require(
320
+ _currentHeaderTarget == minedTarget,
321
+ "Unexpected target change after retarget"
322
+ );
323
+ }
324
+
325
+ previousHeaderDigest = _currentDigest;
326
+ }
327
+
328
+ currentEpoch = currentEpoch + 1;
329
+
330
+ epochs[currentEpoch] = Epoch(
331
+ uint32(epochStartTimestamp),
332
+ uint224(minedTarget)
333
+ );
334
+
335
+ uint256 oldDifficulty = currentEpochDifficulty;
336
+ uint256 newDifficulty = BTCUtils.calculateDifficulty(minedTarget);
337
+
338
+ prevEpochDifficulty = oldDifficulty;
339
+ currentEpochDifficulty = newDifficulty;
340
+
341
+ emit Retarget(oldDifficulty, newDifficulty);
342
+ }
343
+
344
+ /// @notice Check whether a given chain of headers should be accepted as
345
+ /// valid within the rules of the relay.
346
+ /// If the validation fails, this function throws an exception.
347
+ /// @param headers A chain of 2 to 2015 bitcoin headers.
348
+ /// @return startingHeaderTimestamp The timestamp of the first header.
349
+ /// @return headerCount The number of headers.
350
+ /// @dev A chain of headers is accepted as valid if:
351
+ /// - Its length is between 2 and 2015 headers.
352
+ /// - Headers in the chain are sequential and refer to previous digests.
353
+ /// - Each header is mined with the correct amount of work.
354
+ /// - The difficulty in each header matches an epoch of the relay,
355
+ /// as determined by the headers' timestamps. The headers must be between
356
+ /// the genesis epoch and the latest proven epoch (inclusive).
357
+ /// If the chain contains a retarget, it is accepted if the retarget has
358
+ /// already been proven to the relay.
359
+ /// If the chain contains blocks of an epoch that has not been proven to
360
+ /// the relay (after a retarget within the header chain, or when the entire
361
+ /// chain falls within an epoch that has not been proven yet), it will be
362
+ /// rejected.
363
+ /// One exception to this is when two subsequent epochs have exactly the
364
+ /// same difficulty; headers from the latter epoch will be accepted if the
365
+ /// previous epoch has been proven to the relay.
366
+ /// This is because it is not possible to distinguish such headers from
367
+ /// headers of the previous epoch.
368
+ ///
369
+ /// If the difficulty increases significantly between relay genesis and the
370
+ /// present, creating fraudulent proofs for earlier epochs becomes easier.
371
+ /// Users of the relay should check the timestamps of valid headers and
372
+ /// only accept appropriately recent ones.
373
+ function validateChain(bytes memory headers)
374
+ external
375
+ view
376
+ returns (uint256 startingHeaderTimestamp, uint256 headerCount)
377
+ {
378
+ require(headers.length % 80 == 0, "Invalid header length");
379
+
380
+ headerCount = headers.length / 80;
381
+
382
+ require(
383
+ headerCount > 1 && headerCount < 2016,
384
+ "Invalid number of headers"
385
+ );
386
+
387
+ startingHeaderTimestamp = headers.extractTimestamp();
388
+
389
+ // Short-circuit the first header's validation.
390
+ // We validate the header here to get the target which is needed to
391
+ // precisely identify the epoch.
392
+ (
393
+ bytes32 previousHeaderDigest,
394
+ uint256 currentHeaderTarget
395
+ ) = validateHeader(headers, 0, bytes32(0));
396
+
397
+ Epoch memory nullEpoch = Epoch(0, 0);
398
+
399
+ uint256 startingEpochNumber = currentEpoch;
400
+ Epoch memory startingEpoch = epochs[startingEpochNumber];
401
+ Epoch memory nextEpoch = nullEpoch;
402
+
403
+ // Find the correct epoch for the given chain
404
+ // Fastest with recent epochs, but able to handle anything after genesis
405
+ //
406
+ // The rules for bitcoin timestamps are:
407
+ // - must be greater than the median of the last 11 blocks' timestamps
408
+ // - must be less than the network-adjusted time +2 hours
409
+ //
410
+ // Because of this, the timestamp of a header may be smaller than the
411
+ // starting time, or greater than the ending time of its epoch.
412
+ // However, a valid timestamp is guaranteed to fall within the window
413
+ // formed by the epochs immediately before and after its timestamp.
414
+ // We can identify cases like these by comparing the targets.
415
+ while (startingHeaderTimestamp < startingEpoch.timestamp) {
416
+ startingEpochNumber -= 1;
417
+ nextEpoch = startingEpoch;
418
+ startingEpoch = epochs[startingEpochNumber];
419
+ }
420
+
421
+ // We have identified the centre of the window,
422
+ // by reaching the most recent epoch whose starting timestamp
423
+ // or reached before the genesis where epoch slots are empty.
424
+ // Therefore check that the timestamp is nonzero.
425
+ require(
426
+ startingEpoch.timestamp > 0,
427
+ "Cannot validate chains before relay genesis"
428
+ );
429
+
430
+ // The targets don't match. This could be because the block is invalid,
431
+ // or it could be because of timestamp inaccuracy.
432
+ // To cover the latter case, check adjacent epochs.
433
+ if (currentHeaderTarget != startingEpoch.target) {
434
+ // The target matches the next epoch.
435
+ // This means we are right at the beginning of the next epoch,
436
+ // and retargets during the chain should not be possible.
437
+ if (currentHeaderTarget == nextEpoch.target) {
438
+ startingEpoch = nextEpoch;
439
+ nextEpoch = nullEpoch;
440
+ }
441
+ // The target doesn't match the next epoch.
442
+ // Therefore the only valid epoch is the previous one.
443
+ // Because the timestamp can't be more than 2 hours into the future
444
+ // we must be right near the end of the epoch,
445
+ // so a retarget is possible.
446
+ else {
447
+ startingEpochNumber -= 1;
448
+ nextEpoch = startingEpoch;
449
+ startingEpoch = epochs[startingEpochNumber];
450
+
451
+ // We have failed to find a match,
452
+ // therefore the target has to be invalid.
453
+ require(
454
+ currentHeaderTarget == startingEpoch.target,
455
+ "Invalid target in header chain"
456
+ );
457
+ }
458
+ }
459
+
460
+ // We've found the correct epoch for the first header.
461
+ // Validate the rest.
462
+ for (uint256 i = 1; i < headerCount; i++) {
463
+ bytes32 currentDigest;
464
+ (currentDigest, currentHeaderTarget) = validateHeader(
465
+ headers,
466
+ i * 80,
467
+ previousHeaderDigest
468
+ );
469
+
470
+ // If the header's target does not match the expected target,
471
+ // check if a retarget is possible.
472
+ //
473
+ // If next epoch timestamp exists, a valid retarget is possible
474
+ // (if next epoch timestamp doesn't exist, either a retarget has
475
+ // already happened in this chain, the relay needs a retarget
476
+ // before this chain can be validated, or a retarget is not allowed
477
+ // because we know the headers are within a timestamp irregularity
478
+ // of the previous retarget).
479
+ //
480
+ // In this case the target must match the next epoch's target,
481
+ // and the header's timestamp must match the epoch's start.
482
+ if (currentHeaderTarget != startingEpoch.target) {
483
+ uint256 currentHeaderTimestamp = headers.extractTimestampAt(
484
+ i * 80
485
+ );
486
+
487
+ require(
488
+ nextEpoch.timestamp != 0 &&
489
+ currentHeaderTarget == nextEpoch.target &&
490
+ currentHeaderTimestamp == nextEpoch.timestamp,
491
+ "Invalid target in header chain"
492
+ );
493
+
494
+ startingEpoch = nextEpoch;
495
+ nextEpoch = nullEpoch;
496
+ }
497
+
498
+ previousHeaderDigest = currentDigest;
499
+ }
500
+
501
+ return (startingHeaderTimestamp, headerCount);
502
+ }
503
+
504
+ /// @notice Get the difficulty of the specified block.
505
+ /// @param blockNumber The number of the block. Must fall within the relay
506
+ /// range (at or after the relay genesis, and at or before the end of the
507
+ /// most recent epoch proven to the relay).
508
+ /// @return The difficulty of the epoch.
509
+ function getBlockDifficulty(uint256 blockNumber)
510
+ external
511
+ view
512
+ returns (uint256)
513
+ {
514
+ return getEpochDifficulty(blockNumber / 2016);
515
+ }
516
+
517
+ /// @notice Get the range of blocks the relay can accept proofs for.
518
+ /// @dev Assumes that the genesis has been set correctly.
519
+ /// Additionally, if the next epoch after the current one has the exact
520
+ /// same difficulty, headers for it can be validated as well.
521
+ /// This function should be used for informative purposes,
522
+ /// e.g. to determine whether a retarget must be provided before submitting
523
+ /// a header chain for validation.
524
+ /// @return relayGenesis The height of the earliest block that can be
525
+ /// included in header chains for the relay to validate.
526
+ /// @return currentEpochEnd The height of the last block that can be
527
+ /// included in header chains for the relay to validate.
528
+ function getRelayRange()
529
+ external
530
+ view
531
+ returns (uint256 relayGenesis, uint256 currentEpochEnd)
532
+ {
533
+ relayGenesis = genesisEpoch * 2016;
534
+ currentEpochEnd = (currentEpoch * 2016) + 2015;
535
+ }
536
+
537
+ /// @notice Returns the difficulty of the current epoch.
538
+ /// @dev returns 0 if the relay is not ready.
539
+ /// @return The difficulty of the current epoch.
540
+ function getCurrentEpochDifficulty() external view returns (uint256) {
541
+ return currentEpochDifficulty;
542
+ }
543
+
544
+ /// @notice Returns the difficulty of the previous epoch.
545
+ /// @dev Returns 0 if the relay is not ready or has not had a retarget.
546
+ /// @return The difficulty of the previous epoch.
547
+ function getPrevEpochDifficulty() external view returns (uint256) {
548
+ return prevEpochDifficulty;
549
+ }
550
+
551
+ function getCurrentAndPrevEpochDifficulty()
552
+ external
553
+ view
554
+ returns (uint256 current, uint256 previous)
555
+ {
556
+ return (currentEpochDifficulty, prevEpochDifficulty);
557
+ }
558
+
559
+ /// @notice Get the difficulty of the specified epoch.
560
+ /// @param epochNumber The number of the epoch (the height of the first
561
+ /// block of the epoch, divided by 2016). Must fall within the relay range.
562
+ /// @return The difficulty of the epoch.
563
+ function getEpochDifficulty(uint256 epochNumber)
564
+ public
565
+ view
566
+ returns (uint256)
567
+ {
568
+ require(epochNumber >= genesisEpoch, "Epoch is before relay genesis");
569
+ require(
570
+ epochNumber <= currentEpoch,
571
+ "Epoch is not proven to the relay yet"
572
+ );
573
+ return BTCUtils.calculateDifficulty(epochs[epochNumber].target);
574
+ }
575
+
576
+ /// @notice Check that the specified header forms a correct chain with the
577
+ /// digest of the previous header (if provided), and has sufficient work.
578
+ /// @param headers The byte array containing the header of interest.
579
+ /// @param start The start of the header in the array.
580
+ /// @param prevDigest The digest of the previous header
581
+ /// (optional; providing zeros for the digest skips the check).
582
+ /// @return digest The digest of the current header.
583
+ /// @return target The PoW target of the header.
584
+ /// @dev Throws an exception if the header's chain or PoW are invalid.
585
+ /// Performs no other validation.
586
+ function validateHeader(
587
+ bytes memory headers,
588
+ uint256 start,
589
+ bytes32 prevDigest
590
+ ) internal view returns (bytes32 digest, uint256 target) {
591
+ // If previous block digest has been provided, require that it matches
592
+ if (prevDigest != bytes32(0)) {
593
+ require(
594
+ headers.validateHeaderPrevHash(start, prevDigest),
595
+ "Invalid chain"
596
+ );
597
+ }
598
+
599
+ // Require that the header has sufficient work for its stated target
600
+ target = headers.extractTargetAt(start);
601
+ digest = headers.hash256Slice(start, 80);
602
+ require(ValidateSPV.validateHeaderWork(digest, target), "Invalid work");
603
+
604
+ return (digest, target);
605
+ }
606
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keep-network/tbtc-v2",
3
- "version": "0.1.1-dev.107+main.32a2e96d37deb6b976acb87ddc9f5cc619bd4849",
3
+ "version": "0.1.1-dev.108+main.e2ff62a8762071163e8711ddd23f28d0ca422c32",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "artifacts/",