@keep-network/tbtc-v2 0.1.1-dev.7 → 0.1.1-dev.70

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