@keep-network/tbtc-v2 0.1.1-dev.6 → 0.1.1-dev.62

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 (101) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/Bank.json +752 -0
  3. package/artifacts/Bridge.json +3962 -0
  4. package/artifacts/Deposit.json +117 -0
  5. package/artifacts/DepositSweep.json +76 -0
  6. package/artifacts/EcdsaDkgValidator.json +532 -0
  7. package/artifacts/EcdsaInactivity.json +156 -0
  8. package/artifacts/Fraud.json +154 -0
  9. package/artifacts/KeepRegistry.json +99 -0
  10. package/artifacts/KeepStake.json +286 -0
  11. package/artifacts/KeepToken.json +711 -0
  12. package/artifacts/KeepTokenStaking.json +483 -0
  13. package/artifacts/MovingFunds.json +227 -0
  14. package/artifacts/NuCypherStakingEscrow.json +256 -0
  15. package/artifacts/NuCypherToken.json +711 -0
  16. package/artifacts/RandomBeaconStub.json +141 -0
  17. package/artifacts/Redemption.json +162 -0
  18. package/artifacts/ReimbursementPool.json +509 -0
  19. package/artifacts/Relay.json +123 -0
  20. package/artifacts/SortitionPool.json +944 -0
  21. package/artifacts/T.json +1148 -0
  22. package/artifacts/TBTC.json +27 -26
  23. package/artifacts/TBTCToken.json +27 -26
  24. package/artifacts/TokenStaking.json +2288 -0
  25. package/artifacts/TokenholderGovernor.json +1795 -0
  26. package/artifacts/TokenholderTimelock.json +1058 -0
  27. package/artifacts/VendingMachine.json +30 -29
  28. package/artifacts/VendingMachineKeep.json +400 -0
  29. package/artifacts/VendingMachineNuCypher.json +400 -0
  30. package/artifacts/WalletRegistry.json +2709 -0
  31. package/artifacts/WalletRegistryGovernance.json +2364 -0
  32. package/artifacts/Wallets.json +186 -0
  33. package/artifacts/solcInputs/05c98d94f96a77da7702c7818a8cadac.json +227 -0
  34. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  35. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  36. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  37. package/build/contracts/bank/Bank.sol/Bank.json +20 -2
  38. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
  39. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
  40. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  41. package/build/contracts/bridge/Bridge.sol/Bridge.json +2470 -132
  42. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  43. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +220 -0
  44. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  45. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  46. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
  47. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
  48. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  49. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  50. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
  51. package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
  52. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
  53. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
  54. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  55. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  56. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  57. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +125 -0
  58. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  59. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
  60. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  61. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  62. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  63. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  64. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  65. package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
  66. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  67. package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
  68. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
  69. package/build/contracts/vault/DonationVault.sol/DonationVault.json +103 -0
  70. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  71. package/build/contracts/vault/IVault.sol/IVault.json +19 -1
  72. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  73. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +36 -18
  74. package/contracts/GovernanceUtils.sol +1 -1
  75. package/contracts/bank/Bank.sol +34 -18
  76. package/contracts/bridge/BitcoinTx.sol +318 -0
  77. package/contracts/bridge/Bridge.sol +1527 -247
  78. package/contracts/bridge/BridgeState.sol +698 -0
  79. package/contracts/bridge/Deposit.sol +266 -0
  80. package/contracts/bridge/DepositSweep.sol +514 -0
  81. package/contracts/bridge/EcdsaLib.sol +45 -0
  82. package/contracts/bridge/Fraud.sol +508 -0
  83. package/contracts/bridge/Heartbeat.sol +107 -0
  84. package/contracts/bridge/IRelay.sol +28 -0
  85. package/contracts/bridge/MovingFunds.sol +1034 -0
  86. package/contracts/bridge/Redemption.sol +868 -0
  87. package/contracts/bridge/VendingMachine.sol +1 -1
  88. package/contracts/bridge/Wallets.sol +550 -0
  89. package/contracts/token/TBTC.sol +1 -1
  90. package/contracts/vault/DonationVault.sol +125 -0
  91. package/contracts/vault/IVault.sol +32 -10
  92. package/contracts/vault/TBTCVault.sol +20 -2
  93. package/deploy/00_resolve_relay.ts +28 -0
  94. package/deploy/04_deploy_bank.ts +27 -0
  95. package/deploy/05_deploy_bridge.ts +67 -0
  96. package/deploy/06_bank_update_bridge.ts +19 -0
  97. package/deploy/07_transfer_ownership.ts +15 -0
  98. package/deploy/08_transfer_governance.ts +20 -0
  99. package/export.json +15711 -475
  100. package/package.json +27 -24
  101. package/artifacts/solcInputs/c4fd2c31cc58f5fe0cc586dd84a84b60.json +0 -125
@@ -0,0 +1,508 @@
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 {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
19
+ import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
20
+ import {CheckBitcoinSigs} from "@keep-network/bitcoin-spv-sol/contracts/CheckBitcoinSigs.sol";
21
+
22
+ import "./BitcoinTx.sol";
23
+ import "./EcdsaLib.sol";
24
+ import "./BridgeState.sol";
25
+ import "./MovingFunds.sol";
26
+ import "./Wallets.sol";
27
+
28
+ /// @title Bridge fraud
29
+ /// @notice The library handles the logic for challenging Bridge wallets that
30
+ /// committed fraud.
31
+ /// @dev Anyone can submit a fraud challenge indicating that a UTXO being under
32
+ /// the wallet control was unlocked by the wallet but was not used
33
+ /// according to the protocol rules. That means the wallet signed
34
+ /// a transaction input pointing to that UTXO and there is a unique
35
+ /// sighash and signature pair associated with that input.
36
+ ///
37
+ /// In order to defeat the challenge, the same wallet public key and
38
+ /// signature must be provided as were used to calculate the sighash during
39
+ /// the challenge. The wallet provides the preimage which produces sighash
40
+ /// used to generate the ECDSA signature that is the subject of the fraud
41
+ /// claim. The fraud challenge defeat attempt will only succeed if the
42
+ /// inputs in the preimage are considered honestly spent by the wallet.
43
+ /// Therefore the transaction spending the UTXO must be proven in the
44
+ /// Bridge before a challenge defeat is called.
45
+ library Fraud {
46
+ using Wallets for BridgeState.Storage;
47
+
48
+ using BytesLib for bytes;
49
+ using BTCUtils for bytes;
50
+ using BTCUtils for uint32;
51
+ using EcdsaLib for bytes;
52
+
53
+ struct FraudChallenge {
54
+ // The address of the party challenging the wallet.
55
+ address challenger;
56
+ // The amount of ETH the challenger deposited.
57
+ uint256 depositAmount;
58
+ // The timestamp the challenge was submitted at.
59
+ uint32 reportedAt;
60
+ // The flag indicating whether the challenge has been resolved.
61
+ bool resolved;
62
+ }
63
+
64
+ event FraudChallengeSubmitted(
65
+ bytes20 walletPubKeyHash,
66
+ bytes32 sighash,
67
+ uint8 v,
68
+ bytes32 r,
69
+ bytes32 s
70
+ );
71
+
72
+ event FraudChallengeDefeated(bytes20 walletPubKeyHash, bytes32 sighash);
73
+
74
+ event FraudChallengeDefeatTimedOut(
75
+ bytes20 walletPubKeyHash,
76
+ bytes32 sighash
77
+ );
78
+
79
+ /// @notice Submits a fraud challenge indicating that a UTXO being under
80
+ /// wallet control was unlocked by the wallet but was not used
81
+ /// according to the protocol rules. That means the wallet signed
82
+ /// a transaction input pointing to that UTXO and there is a unique
83
+ /// sighash and signature pair associated with that input. This
84
+ /// function uses those parameters to create a fraud accusation that
85
+ /// proves a given transaction input unlocking the given UTXO was
86
+ /// actually signed by the wallet. This function cannot determine
87
+ /// whether the transaction was actually broadcast and the input was
88
+ /// consumed in a fraudulent way so it just opens a challenge period
89
+ /// during which the wallet can defeat the challenge by submitting
90
+ /// proof of a transaction that consumes the given input according
91
+ /// to protocol rules. To prevent spurious allegations, the caller
92
+ /// must deposit ETH that is returned back upon justified fraud
93
+ /// challenge or confiscated otherwise
94
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
95
+ /// and unprefixed format (64 bytes)
96
+ /// @param sighash The hash that was used to produce the ECDSA signature
97
+ /// that is the subject of the fraud claim. This hash is constructed
98
+ /// by applying double SHA-256 over a serialized subset of the
99
+ /// transaction. The exact subset used as hash preimage depends on
100
+ /// the transaction input the signature is produced for. See BIP-143
101
+ /// for reference
102
+ /// @param signature Bitcoin signature in the R/S/V format
103
+ /// @dev Requirements:
104
+ /// - Wallet behind `walletPublicKey` must be in Live or MovingFunds
105
+ /// or Closing state
106
+ /// - The challenger must send appropriate amount of ETH used as
107
+ /// fraud challenge deposit
108
+ /// - The signature (represented by r, s and v) must be generated by
109
+ /// the wallet behind `walletPublicKey` during signing of `sighash`
110
+ /// - Wallet can be challenged for the given signature only once
111
+ function submitFraudChallenge(
112
+ BridgeState.Storage storage self,
113
+ bytes calldata walletPublicKey,
114
+ bytes32 sighash,
115
+ BitcoinTx.RSVSignature calldata signature
116
+ ) external {
117
+ require(
118
+ msg.value >= self.fraudChallengeDepositAmount,
119
+ "The amount of ETH deposited is too low"
120
+ );
121
+
122
+ require(
123
+ CheckBitcoinSigs.checkSig(
124
+ walletPublicKey,
125
+ sighash,
126
+ signature.v,
127
+ signature.r,
128
+ signature.s
129
+ ),
130
+ "Signature verification failure"
131
+ );
132
+
133
+ bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
134
+ walletPublicKey.slice32(0),
135
+ walletPublicKey.slice32(32)
136
+ );
137
+ bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
138
+
139
+ Wallets.Wallet storage wallet = self.registeredWallets[
140
+ walletPubKeyHash
141
+ ];
142
+
143
+ require(
144
+ wallet.state == Wallets.WalletState.Live ||
145
+ wallet.state == Wallets.WalletState.MovingFunds ||
146
+ wallet.state == Wallets.WalletState.Closing,
147
+ "Wallet must be in Live or MovingFunds or Closing state"
148
+ );
149
+
150
+ uint256 challengeKey = uint256(
151
+ keccak256(abi.encodePacked(walletPublicKey, sighash))
152
+ );
153
+
154
+ FraudChallenge storage challenge = self.fraudChallenges[challengeKey];
155
+ require(challenge.reportedAt == 0, "Fraud challenge already exists");
156
+
157
+ challenge.challenger = msg.sender;
158
+ challenge.depositAmount = msg.value;
159
+ /* solhint-disable-next-line not-rely-on-time */
160
+ challenge.reportedAt = uint32(block.timestamp);
161
+ challenge.resolved = false;
162
+ // slither-disable-next-line reentrancy-events
163
+ emit FraudChallengeSubmitted(
164
+ walletPubKeyHash,
165
+ sighash,
166
+ signature.v,
167
+ signature.r,
168
+ signature.s
169
+ );
170
+ }
171
+
172
+ /// @notice Allows to defeat a pending fraud challenge against a wallet if
173
+ /// the transaction that spends the UTXO follows the protocol rules.
174
+ /// In order to defeat the challenge the same `walletPublicKey` and
175
+ /// signature (represented by `r`, `s` and `v`) must be provided as
176
+ /// were used to calculate the sighash during input signing.
177
+ /// The fraud challenge defeat attempt will only succeed if the
178
+ /// inputs in the preimage are considered honestly spent by the
179
+ /// wallet. Therefore the transaction spending the UTXO must be
180
+ /// proven in the Bridge before a challenge defeat is called.
181
+ /// If successfully defeated, the fraud challenge is marked as
182
+ /// resolved and the amount of ether deposited by the challenger is
183
+ /// sent to the treasury.
184
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
185
+ /// and unprefixed format (64 bytes)
186
+ /// @param preimage The preimage which produces sighash used to generate the
187
+ /// ECDSA signature that is the subject of the fraud claim. It is a
188
+ /// serialized subset of the transaction. The exact subset used as
189
+ /// the preimage depends on the transaction input the signature is
190
+ /// produced for. See BIP-143 for reference
191
+ /// @param witness Flag indicating whether the preimage was produced for a
192
+ /// witness input. True for witness, false for non-witness input.
193
+ /// @dev Requirements:
194
+ /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
195
+ /// must identify an open fraud challenge
196
+ /// - the preimage must be a valid preimage of a transaction generated
197
+ /// according to the protocol rules and already proved in the Bridge
198
+ /// - before a defeat attempt is made the transaction that spends the
199
+ /// given UTXO must be proven in the Bridge
200
+ function defeatFraudChallenge(
201
+ BridgeState.Storage storage self,
202
+ bytes calldata walletPublicKey,
203
+ bytes calldata preimage,
204
+ bool witness
205
+ ) external {
206
+ bytes32 sighash = preimage.hash256();
207
+
208
+ uint256 challengeKey = uint256(
209
+ keccak256(abi.encodePacked(walletPublicKey, sighash))
210
+ );
211
+
212
+ FraudChallenge storage challenge = self.fraudChallenges[challengeKey];
213
+
214
+ require(challenge.reportedAt > 0, "Fraud challenge does not exist");
215
+ require(
216
+ !challenge.resolved,
217
+ "Fraud challenge has already been resolved"
218
+ );
219
+
220
+ // Ensure SIGHASH_ALL type was used during signing, which is represented
221
+ // by type value `1`.
222
+ require(extractSighashType(preimage) == 1, "Wrong sighash type");
223
+
224
+ uint256 utxoKey = witness
225
+ ? extractUtxoKeyFromWitnessPreimage(preimage)
226
+ : extractUtxoKeyFromNonWitnessPreimage(preimage);
227
+
228
+ // Check that the UTXO key identifies a correctly spent UTXO.
229
+ require(
230
+ self.deposits[utxoKey].sweptAt > 0 ||
231
+ self.spentMainUTXOs[utxoKey] ||
232
+ self.movedFundsSweepRequests[utxoKey].state ==
233
+ MovingFunds.MovedFundsSweepRequestState.Processed,
234
+ "Spent UTXO not found among correctly spent UTXOs"
235
+ );
236
+
237
+ // Mark the challenge as resolved as it was successfully defeated
238
+ challenge.resolved = true;
239
+
240
+ // Send the ether deposited by the challenger to the treasury
241
+ /* solhint-disable avoid-low-level-calls */
242
+ // slither-disable-next-line low-level-calls,unchecked-lowlevel,arbitrary-send
243
+ self.treasury.call{gas: 100000, value: challenge.depositAmount}("");
244
+ /* solhint-enable avoid-low-level-calls */
245
+
246
+ bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
247
+ walletPublicKey.slice32(0),
248
+ walletPublicKey.slice32(32)
249
+ );
250
+ bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
251
+ // slither-disable-next-line reentrancy-events
252
+ emit FraudChallengeDefeated(walletPubKeyHash, sighash);
253
+ }
254
+
255
+ /// @notice Notifies about defeat timeout for the given fraud challenge.
256
+ /// Can be called only if there was a fraud challenge identified by
257
+ /// the provided `walletPublicKey` and `sighash` and it was not
258
+ /// defeated on time. The amount of time that needs to pass after a
259
+ /// fraud challenge is reported is indicated by the
260
+ /// `challengeDefeatTimeout`. After a successful fraud challenge
261
+ /// defeat timeout notification the fraud challenge is marked as
262
+ /// resolved, the stake of each operator is slashed, the ether
263
+ /// deposited is returned to the challenger and the challenger is
264
+ /// rewarded.
265
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
266
+ /// and unprefixed format (64 bytes)
267
+ /// @param walletMembersIDs Identifiers of the wallet signing group members
268
+ /// @param sighash The hash that was used to produce the ECDSA signature
269
+ /// that is the subject of the fraud claim. This hash is constructed
270
+ /// by applying double SHA-256 over a serialized subset of the
271
+ /// transaction. The exact subset used as hash preimage depends on
272
+ /// the transaction input the signature is produced for. See BIP-143
273
+ /// for reference
274
+ /// @dev Requirements:
275
+ /// - The wallet must be in the Live or MovingFunds or Closing or
276
+ /// Terminated state
277
+ /// - The `walletPublicKey` and `sighash` must identify an open fraud
278
+ /// challenge
279
+ /// - The expression `keccak256(abi.encode(walletMembersIDs))` must
280
+ /// be exactly the same as the hash stored under `membersIdsHash`
281
+ /// for the given `walletID`. Those IDs are not directly stored
282
+ /// in the contract for gas efficiency purposes but they can be
283
+ /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`
284
+ /// events of the `WalletRegistry` contract
285
+ /// - The amount of time indicated by `challengeDefeatTimeout` must pass
286
+ /// after the challenge was reported
287
+ function notifyFraudChallengeDefeatTimeout(
288
+ BridgeState.Storage storage self,
289
+ bytes calldata walletPublicKey,
290
+ uint32[] calldata walletMembersIDs,
291
+ bytes32 sighash
292
+ ) external {
293
+ uint256 challengeKey = uint256(
294
+ keccak256(abi.encodePacked(walletPublicKey, sighash))
295
+ );
296
+
297
+ FraudChallenge storage challenge = self.fraudChallenges[challengeKey];
298
+
299
+ require(challenge.reportedAt > 0, "Fraud challenge does not exist");
300
+
301
+ require(
302
+ !challenge.resolved,
303
+ "Fraud challenge has already been resolved"
304
+ );
305
+
306
+ require(
307
+ /* solhint-disable-next-line not-rely-on-time */
308
+ block.timestamp >=
309
+ challenge.reportedAt + self.fraudChallengeDefeatTimeout,
310
+ "Fraud challenge defeat period did not time out yet"
311
+ );
312
+
313
+ challenge.resolved = true;
314
+ // Return the ether deposited by the challenger
315
+ /* solhint-disable avoid-low-level-calls */
316
+ // slither-disable-next-line low-level-calls,unchecked-lowlevel
317
+ challenge.challenger.call{gas: 100000, value: challenge.depositAmount}(
318
+ ""
319
+ );
320
+ /* solhint-enable avoid-low-level-calls */
321
+
322
+ bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
323
+ walletPublicKey.slice32(0),
324
+ walletPublicKey.slice32(32)
325
+ );
326
+ bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
327
+
328
+ Wallets.Wallet storage wallet = self.registeredWallets[
329
+ walletPubKeyHash
330
+ ];
331
+
332
+ Wallets.WalletState walletState = wallet.state;
333
+
334
+ if (
335
+ walletState == Wallets.WalletState.Live ||
336
+ walletState == Wallets.WalletState.MovingFunds ||
337
+ walletState == Wallets.WalletState.Closing
338
+ ) {
339
+ self.terminateWallet(walletPubKeyHash);
340
+
341
+ self.ecdsaWalletRegistry.seize(
342
+ self.fraudSlashingAmount,
343
+ self.fraudNotifierRewardMultiplier,
344
+ challenge.challenger,
345
+ wallet.ecdsaWalletID,
346
+ walletMembersIDs
347
+ );
348
+ } else if (walletState == Wallets.WalletState.Terminated) {
349
+ // This is a special case when the wallet was already terminated
350
+ // due to a previous deliberate protocol violation. In that
351
+ // case, this function should be still callable for other fraud
352
+ // challenges timeouts in order to let the challenger unlock its
353
+ // ETH deposit back. However, the wallet termination logic is
354
+ // not called and the challenger is not rewarded.
355
+ } else {
356
+ revert(
357
+ "Wallet must be in Live or MovingFunds or Closing or Terminated state"
358
+ );
359
+ }
360
+
361
+ // slither-disable-next-line reentrancy-events
362
+ emit FraudChallengeDefeatTimedOut(walletPubKeyHash, sighash);
363
+ }
364
+
365
+ /// @notice Extracts the UTXO keys from the given preimage used during
366
+ /// signing of a witness input.
367
+ /// @param preimage The preimage which produces sighash used to generate the
368
+ /// ECDSA signature that is the subject of the fraud claim. It is a
369
+ /// serialized subset of the transaction. The exact subset used as
370
+ /// the preimage depends on the transaction input the signature is
371
+ /// produced for. See BIP-143 for reference
372
+ /// @return utxoKey UTXO key that identifies spent input.
373
+ function extractUtxoKeyFromWitnessPreimage(bytes calldata preimage)
374
+ internal
375
+ pure
376
+ returns (uint256 utxoKey)
377
+ {
378
+ // The expected structure of the preimage created during signing of a
379
+ // witness input:
380
+ // - transaction version (4 bytes)
381
+ // - hash of previous outpoints of all inputs (32 bytes)
382
+ // - hash of sequences of all inputs (32 bytes)
383
+ // - outpoint (hash + index) of the input being signed (36 bytes)
384
+ // - the unlocking script of the input (variable length)
385
+ // - value of the outpoint (8 bytes)
386
+ // - sequence of the input being signed (4 bytes)
387
+ // - hash of all outputs (32 bytes)
388
+ // - transaction locktime (4 bytes)
389
+ // - sighash type (4 bytes)
390
+
391
+ // See Bitcoin's BIP-143 for reference:
392
+ // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.
393
+
394
+ // The outpoint (hash and index) is located at the constant offset of
395
+ // 68 (4 + 32 + 32).
396
+ bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(68);
397
+ uint32 outpointIndex = BTCUtils.reverseUint32(
398
+ uint32(preimage.extractTxIndexLeAt(68))
399
+ );
400
+
401
+ return
402
+ uint256(keccak256(abi.encodePacked(outpointTxHash, outpointIndex)));
403
+ }
404
+
405
+ /// @notice Extracts the UTXO key from the given preimage used during
406
+ /// signing of a non-witness input.
407
+ /// @param preimage The preimage which produces sighash used to generate the
408
+ /// ECDSA signature that is the subject of the fraud claim. It is a
409
+ /// serialized subset of the transaction. The exact subset used as
410
+ /// the preimage depends on the transaction input the signature is
411
+ /// produced for. See BIP-143 for reference
412
+ /// @return utxoKey UTXO key that identifies spent input.
413
+ function extractUtxoKeyFromNonWitnessPreimage(bytes calldata preimage)
414
+ internal
415
+ pure
416
+ returns (uint256 utxoKey)
417
+ {
418
+ // The expected structure of the preimage created during signing of a
419
+ // non-witness input:
420
+ // - transaction version (4 bytes)
421
+ // - number of inputs written as compactSize uint (1 byte, 3 bytes,
422
+ // 5 bytes or 9 bytes)
423
+ // - for each input
424
+ // - outpoint (hash and index) (36 bytes)
425
+ // - unlocking script for the input being signed (variable length)
426
+ // or `00` for all other inputs (1 byte)
427
+ // - input sequence (4 bytes)
428
+ // - number of outputs written as compactSize uint (1 byte, 3 bytes,
429
+ // 5 bytes or 9 bytes)
430
+ // - outputs (variable length)
431
+ // - transaction locktime (4 bytes)
432
+ // - sighash type (4 bytes)
433
+
434
+ // See example for reference:
435
+ // https://en.bitcoin.it/wiki/OP_CHECKSIG#Code_samples_and_raw_dumps.
436
+
437
+ // The input data begins at the constant offset of 4 (the first 4 bytes
438
+ // are for the transaction version).
439
+ (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = preimage
440
+ .parseVarIntAt(4);
441
+
442
+ // To determine the first input starting index, we must jump 4 bytes
443
+ // over the transaction version length and the compactSize uint which
444
+ // prepends the input vector. One byte must be added because
445
+ // `BtcUtils.parseVarInt` does not include compactSize uint tag in the
446
+ // returned length.
447
+ //
448
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
449
+ // returns `0`, so we jump over one byte of compactSize uint.
450
+ //
451
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
452
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
453
+ // tag byte included) so we need to jump over 1+2 bytes of
454
+ // compactSize uint.
455
+ //
456
+ // Please refer `BTCUtils` library and compactSize uint
457
+ // docs in `BitcoinTx` library for more details.
458
+ uint256 inputStartingIndex = 4 + 1 + inputsCompactSizeUintLength;
459
+
460
+ for (uint256 i = 0; i < inputsCount; i++) {
461
+ uint256 inputLength = preimage.determineInputLengthAt(
462
+ inputStartingIndex
463
+ );
464
+
465
+ (, uint256 scriptSigLength) = preimage.extractScriptSigLenAt(
466
+ inputStartingIndex
467
+ );
468
+
469
+ if (scriptSigLength > 0) {
470
+ // The input this preimage was generated for was found.
471
+ // All the other inputs in the preimage are marked with a null
472
+ // scriptSig ("00") which has length of 1.
473
+ bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(
474
+ inputStartingIndex
475
+ );
476
+ uint32 outpointIndex = BTCUtils.reverseUint32(
477
+ uint32(preimage.extractTxIndexLeAt(inputStartingIndex))
478
+ );
479
+
480
+ utxoKey = uint256(
481
+ keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
482
+ );
483
+
484
+ break;
485
+ }
486
+
487
+ inputStartingIndex += inputLength;
488
+ }
489
+
490
+ return utxoKey;
491
+ }
492
+
493
+ /// @notice Extracts the sighash type from the given preimage.
494
+ /// @param preimage Serialized subset of the transaction. See BIP-143 for
495
+ /// reference
496
+ /// @dev Sighash type is stored as the last 4 bytes in the preimage (little
497
+ /// endian).
498
+ /// @return sighashType Sighash type as a 32-bit integer.
499
+ function extractSighashType(bytes calldata preimage)
500
+ internal
501
+ pure
502
+ returns (uint32 sighashType)
503
+ {
504
+ bytes4 sighashTypeBytes = preimage.slice4(preimage.length - 4);
505
+ uint32 sighashTypeLE = uint32(sighashTypeBytes);
506
+ return sighashTypeLE.reverseUint32();
507
+ }
508
+ }
@@ -0,0 +1,107 @@
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 {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
19
+
20
+ /// @title Bridge wallet heartbeat
21
+ /// @notice The library establishes expected format for heartbeat messages
22
+ /// signed by wallet ECDSA signing group. Heartbeat messages are
23
+ /// constructed in such a way that they can not be used as a Bitcoin
24
+ /// transaction preimages.
25
+ /// @dev The smallest Bitcoin non-coinbase transaction is a one spending an
26
+ /// OP_TRUE anyonecanspend output and creating 1 OP_TRUE anyonecanspend
27
+ /// output. Such a transaction has 61 bytes (see `BitcoinTx` documentation):
28
+ /// 4 bytes for version
29
+ /// 1 byte for tx_in_count
30
+ /// 36 bytes for tx_in.previous_output
31
+ /// 1 byte for tx_in.script_bytes (value: 0)
32
+ /// 0 bytes for tx_in.signature_script
33
+ /// 4 bytes for tx_in.sequence
34
+ /// 1 byte for tx_out_count
35
+ /// 8 bytes for tx_out.value
36
+ /// 1 byte for tx_out.pk_script_bytes
37
+ /// 1 byte for tx_out.pk_script
38
+ /// 4 bytes for lock_time
39
+ ///
40
+ ///
41
+ /// The smallest Bitcoin coinbase transaction is a one creating
42
+ /// 1 OP_TRUE anyonecanspend output and having an empty coinbase script.
43
+ /// Such a transaction has 65 bytes:
44
+ /// 4 bytes for version
45
+ /// 1 byte for tx_in_count
46
+ /// 32 bytes for tx_in.hash (all 0x00)
47
+ /// 4 bytes for tx_in.index (all 0xff)
48
+ /// 1 byte for tx_in.script_bytes (value: 0)
49
+ /// 4 bytes for tx_in.height
50
+ /// 0 byte for tx_in.coinbase_script
51
+ /// 4 bytes for tx_in.sequence
52
+ /// 1 byte for tx_out_count
53
+ /// 8 bytes for tx_out.value
54
+ /// 1 byte for tx_out.pk_script_bytes
55
+ /// 1 byte for tx_out.pk_script
56
+ /// 4 bytes for lock_time
57
+ ///
58
+ ///
59
+ /// A SIGHASH flag is used to indicate which part of the transaction is
60
+ /// signed by the ECDSA signature. There are currently 3 flags:
61
+ /// SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, and different combinations
62
+ /// of these flags.
63
+ ///
64
+ /// No matter the SIGHASH flag and no matter the combination, the following
65
+ /// fields from the transaction are always included in the constructed
66
+ /// preimage:
67
+ /// 4 bytes for version
68
+ /// 36 bytes for tx_in.previous_output (or tx_in.hash + tx_in.index for coinbase)
69
+ /// 4 bytes for lock_time
70
+ ///
71
+ /// Additionally, the last 4 bytes of the preimage determines the SIGHASH
72
+ /// flag.
73
+ ///
74
+ /// This is enough to say there is no way the preimage could be shorter
75
+ /// than 4 + 36 + 4 + 4 = 48 bytes.
76
+ ///
77
+ /// For this reason, we construct the heartbeat message, as a 16-byte
78
+ /// message. The first 8 bytes are 0xffffffffffffffff. The last 8 bytes
79
+ /// are for an arbitrary uint64, being a signed heartbeat nonce (for
80
+ /// example, the last Ethereum block hash).
81
+ library Heartbeat {
82
+ using BytesLib for bytes;
83
+
84
+ /// @notice Determines if the signed byte array is a valid, non-fraudulent
85
+ /// heartbeat message.
86
+ /// @param message Message signed by the wallet. It is a potential heartbeat
87
+ /// message, Bitcoin transaction preimage, or an arbitrary signed
88
+ /// bytes
89
+ /// @dev Wallet heartbeat message must be exactly 16 bytes long with the first
90
+ /// 8 bytes set to 0xffffffffffffffff.
91
+ /// @return True if valid heartbeat message, false otherwise.
92
+ function isValidHeartbeatMessage(bytes calldata message)
93
+ internal
94
+ pure
95
+ returns (bool)
96
+ {
97
+ if (message.length != 16) {
98
+ return false;
99
+ }
100
+
101
+ if (message.slice8(0) != 0xffffffffffffffff) {
102
+ return false;
103
+ }
104
+
105
+ return true;
106
+ }
107
+ }
@@ -0,0 +1,28 @@
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
+ /// @title Interface for the Bitcoin relay
19
+ /// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
20
+ /// provides the difficulty of the previous and current epoch. One
21
+ /// difficulty epoch spans 2016 blocks.
22
+ interface IRelay {
23
+ /// @notice Returns the difficulty of the current epoch.
24
+ function getCurrentEpochDifficulty() external view returns (uint256);
25
+
26
+ /// @notice Returns the difficulty of the previous epoch.
27
+ function getPrevEpochDifficulty() external view returns (uint256);
28
+ }