@keep-network/tbtc-v2 0.1.1-dev.30 → 0.1.1-dev.33

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 (29) hide show
  1. package/artifacts/TBTC.json +3 -3
  2. package/artifacts/TBTCToken.json +3 -3
  3. package/artifacts/VendingMachine.json +10 -10
  4. package/artifacts/solcInputs/{087a7a27c8cd210b7c63e594263dfed9.json → 7e4974a220c8c12bc2c31c8235131416.json} +10 -4
  5. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  6. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  7. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  8. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  9. package/build/contracts/bridge/Bridge.sol/Bridge.json +234 -82
  10. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
  11. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  12. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +10 -0
  13. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  14. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  15. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  16. package/build/contracts/bridge/Frauds.sol/Frauds.dbg.json +1 -1
  17. package/build/contracts/bridge/Frauds.sol/Frauds.json +2 -2
  18. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  19. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  20. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  21. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  22. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  23. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  24. package/contracts/bridge/Bridge.sol +556 -421
  25. package/contracts/bridge/BridgeState.sol +54 -0
  26. package/contracts/bridge/Deposit.sol +247 -0
  27. package/contracts/bridge/Frauds.sol +5 -7
  28. package/contracts/bridge/Wallets.sol +89 -19
  29. package/package.json +1 -1
@@ -22,6 +22,7 @@ import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
22
22
  import {IWalletOwner as EcdsaWalletOwner} from "@keep-network/ecdsa/contracts/api/IWalletOwner.sol";
23
23
 
24
24
  import "../bank/Bank.sol";
25
+ import "./BridgeState.sol";
25
26
  import "./BitcoinTx.sol";
26
27
  import "./EcdsaLib.sol";
27
28
  import "./Wallets.sol";
@@ -58,59 +59,20 @@ interface IRelay {
58
59
  /// wallet informs the Bridge about the sweep increasing appropriate
59
60
  /// balances in the Bank.
60
61
  /// @dev Bridge is an upgradeable component of the Bank.
62
+ ///
63
+ /// TODO: All wallets-related operations that are currently done directly
64
+ /// by the Bridge can be probably delegated to the Wallets library.
65
+ /// Examples of such operations are main UTXO or pending redemptions
66
+ /// value updates.
61
67
  contract Bridge is Ownable, EcdsaWalletOwner {
62
- using BTCUtils for bytes;
63
- using BTCUtils for uint256;
64
- using BytesLib for bytes;
68
+ using BridgeState for BridgeState.Storage;
69
+ using Deposit for BridgeState.Storage;
65
70
  using Frauds for Frauds.Data;
66
71
  using Wallets for Wallets.Data;
67
72
 
68
- /// @notice Represents data which must be revealed by the depositor during
69
- /// deposit reveal.
70
- struct RevealInfo {
71
- // Index of the funding output belonging to the funding transaction.
72
- uint32 fundingOutputIndex;
73
- // Ethereum depositor address.
74
- address depositor;
75
- // The blinding factor as 8 bytes. Byte endianness doesn't matter
76
- // as this factor is not interpreted as uint.
77
- bytes8 blindingFactor;
78
- // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
79
- // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
80
- bytes20 walletPubKeyHash;
81
- // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
82
- // that can be used to make the deposit refund after the refund
83
- // locktime passes. Hashed in the HASH160 Bitcoin opcode style.
84
- bytes20 refundPubKeyHash;
85
- // The refund locktime (4-byte LE). Interpreted according to locktime
86
- // parsing rules described in:
87
- // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
88
- // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
89
- // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
90
- bytes4 refundLocktime;
91
- // Address of the Bank vault to which the deposit is routed to.
92
- // Optional, can be 0x0. The vault must be trusted by the Bridge.
93
- address vault;
94
- }
95
-
96
- /// @notice Represents tBTC deposit data.
97
- struct DepositRequest {
98
- // Ethereum depositor address.
99
- address depositor;
100
- // Deposit amount in satoshi.
101
- uint64 amount;
102
- // UNIX timestamp the deposit was revealed at.
103
- uint32 revealedAt;
104
- // Address of the Bank vault the deposit is routed to.
105
- // Optional, can be 0x0.
106
- address vault;
107
- // Treasury TBTC fee in satoshi at the moment of deposit reveal.
108
- uint64 treasuryFee;
109
- // UNIX timestamp the deposit was swept at. Note this is not the
110
- // time when the deposit was swept on the Bitcoin chain but actually
111
- // the time when the sweep proof was delivered to the Ethereum chain.
112
- uint32 sweptAt;
113
- }
73
+ using BTCUtils for bytes;
74
+ using BTCUtils for uint256;
75
+ using BytesLib for bytes;
114
76
 
115
77
  /// @notice Represents an outcome of the sweep Bitcoin transaction
116
78
  /// inputs processing.
@@ -188,29 +150,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
188
150
  /// Treasury takes part in the operators rewarding process.
189
151
  address public immutable treasury;
190
152
 
191
- /// TODO: Make it governable.
192
- /// @notice The minimal amount that can be requested for deposit.
193
- /// Value of this parameter must take into account the value of
194
- /// `depositTreasuryFeeDivisor` and `depositTxMaxFee`
195
- /// parameters in order to make requests that can incur the
196
- /// treasury and transaction fee and still satisfy the depositor.
197
- uint64 public depositDustThreshold;
198
-
199
- /// TODO: Make it governable.
200
- /// @notice Divisor used to compute the treasury fee taken from each
201
- /// deposit and transferred to the treasury upon sweep proof
202
- /// submission. That fee is computed as follows:
203
- /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`
204
- /// For example, if the treasury fee needs to be 2% of each deposit,
205
- /// the `depositTreasuryFeeDivisor` should be set to `50`
206
- /// because `1/50 = 0.02 = 2%`.
207
- uint64 public depositTreasuryFeeDivisor;
153
+ BridgeState.Storage internal self;
208
154
 
209
155
  /// TODO: Make it governable.
210
156
  /// @notice Maximum amount of BTC transaction fee that can be incurred by
211
157
  /// each swept deposit being part of the given sweep
212
158
  /// transaction. If the maximum BTC transaction fee is exceeded,
213
159
  /// such transaction is considered a fraud.
160
+ /// @dev This is a per-deposit input max fee for the sweep transaction.
214
161
  uint64 public depositTxMaxFee;
215
162
 
216
163
  /// TODO: Make it governable.
@@ -236,6 +183,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
236
183
  /// each redemption request being part of the given redemption
237
184
  /// transaction. If the maximum BTC transaction fee is exceeded, such
238
185
  /// transaction is considered a fraud.
186
+ /// @dev This is a per-redemption output max fee for the redemption transaction.
239
187
  uint64 public redemptionTxMaxFee;
240
188
 
241
189
  /// TODO: Make it governable.
@@ -246,25 +194,15 @@ contract Bridge is Ownable, EcdsaWalletOwner {
246
194
  /// to the redeemer in full amount.
247
195
  uint256 public redemptionTimeout;
248
196
 
249
- /// @notice Indicates if the vault with the given address is trusted or not.
250
- /// Depositors can route their revealed deposits only to trusted
251
- /// vaults and have trusted vaults notified about new deposits as
252
- /// soon as these deposits get swept. Vaults not trusted by the
253
- /// Bridge can still be used by Bank balance owners on their own
254
- /// responsibility - anyone can approve their Bank balance to any
255
- /// address.
256
- mapping(address => bool) public isVaultTrusted;
257
-
258
- /// @notice Collection of all revealed deposits indexed by
259
- /// keccak256(fundingTxHash | fundingOutputIndex).
260
- /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
261
- /// and fundingOutputIndex an uint32. This mapping may contain valid
262
- /// and invalid deposits and the wallet is responsible for
263
- /// validating them before attempting to execute a sweep.
264
- mapping(uint256 => DepositRequest) public deposits;
197
+ /// TODO: Make it governable.
198
+ /// @notice Maximum amount of the total BTC transaction fee that is
199
+ /// acceptable in a single moving funds transaction.
200
+ /// @dev This is a TOTAL max fee for the moving funds transaction. Note that
201
+ /// `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`
202
+ /// if per single redemption. `movingFundsTxMaxTotalFee` is a total fee
203
+ /// for the entire transaction.
204
+ uint64 public movingFundsTxMaxTotalFee;
265
205
 
266
- //TODO: Remember to update this map when implementing transferring funds
267
- // between wallets (insert the main UTXO that was used as the input).
268
206
  /// @notice Collection of main UTXOs that are honestly spent indexed by
269
207
  /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash
270
208
  /// is bytes32 (ordered as in Bitcoin internally) and
@@ -406,6 +344,11 @@ contract Bridge is Ownable, EcdsaWalletOwner {
406
344
  bytes32 sighash
407
345
  );
408
346
 
347
+ event MovingFundsCompleted(
348
+ bytes20 walletPubKeyHash,
349
+ bytes32 movingFundsTxHash
350
+ );
351
+
409
352
  constructor(
410
353
  address _bank,
411
354
  address _relay,
@@ -425,13 +368,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
425
368
  txProofDifficultyFactor = _txProofDifficultyFactor;
426
369
 
427
370
  // TODO: Revisit initial values.
428
- depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
429
- depositTxMaxFee = 1000; // 1000 satoshi
430
- depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
371
+ self.depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
372
+ depositTxMaxFee = 10000; // 10000 satoshi
373
+ self.depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
431
374
  redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
432
375
  redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
433
- redemptionTxMaxFee = 1000; // 1000 satoshi
376
+ redemptionTxMaxFee = 10000; // 10000 satoshi
434
377
  redemptionTimeout = 172800; // 48 hours
378
+ movingFundsTxMaxTotalFee = 10000; // 10000 satoshi
379
+
380
+ // TODO: Revisit initial values.
435
381
  frauds.setSlashingAmount(10000 * 1e18); // 10000 T
436
382
  frauds.setNotifierRewardMultiplier(100); // 100%
437
383
  frauds.setChallengeDefeatTimeout(7 days);
@@ -500,7 +446,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
500
446
  /// @param isTrusted flag indicating whether the vault is trusted or not
501
447
  /// @dev Can only be called by the Governance.
502
448
  function setVaultStatus(address vault, bool isTrusted) external onlyOwner {
503
- isVaultTrusted[vault] = isTrusted;
449
+ self.isVaultTrusted[vault] = isTrusted;
504
450
  emit VaultStatusUpdated(vault, isTrusted);
505
451
  }
506
452
 
@@ -655,124 +601,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
655
601
  /// deposit script unlocks to receive their BTC back.
656
602
  function revealDeposit(
657
603
  BitcoinTx.Info calldata fundingTx,
658
- RevealInfo calldata reveal
604
+ Deposit.RevealInfo calldata reveal
659
605
  ) external {
660
- require(
661
- wallets.registeredWallets[reveal.walletPubKeyHash].state ==
662
- Wallets.WalletState.Live,
663
- "Wallet is not in Live state"
664
- );
665
-
666
- require(
667
- reveal.vault == address(0) || isVaultTrusted[reveal.vault],
668
- "Vault is not trusted"
669
- );
670
-
671
- // TODO: Should we enforce a specific locktime at contract level?
672
-
673
- bytes memory expectedScript = abi.encodePacked(
674
- hex"14", // Byte length of depositor Ethereum address.
675
- reveal.depositor,
676
- hex"75", // OP_DROP
677
- hex"08", // Byte length of blinding factor value.
678
- reveal.blindingFactor,
679
- hex"75", // OP_DROP
680
- hex"76", // OP_DUP
681
- hex"a9", // OP_HASH160
682
- hex"14", // Byte length of a compressed Bitcoin public key hash.
683
- reveal.walletPubKeyHash,
684
- hex"87", // OP_EQUAL
685
- hex"63", // OP_IF
686
- hex"ac", // OP_CHECKSIG
687
- hex"67", // OP_ELSE
688
- hex"76", // OP_DUP
689
- hex"a9", // OP_HASH160
690
- hex"14", // Byte length of a compressed Bitcoin public key hash.
691
- reveal.refundPubKeyHash,
692
- hex"88", // OP_EQUALVERIFY
693
- hex"04", // Byte length of refund locktime value.
694
- reveal.refundLocktime,
695
- hex"b1", // OP_CHECKLOCKTIMEVERIFY
696
- hex"75", // OP_DROP
697
- hex"ac", // OP_CHECKSIG
698
- hex"68" // OP_ENDIF
699
- );
700
-
701
- bytes memory fundingOutput = fundingTx
702
- .outputVector
703
- .extractOutputAtIndex(reveal.fundingOutputIndex);
704
- bytes memory fundingOutputHash = fundingOutput.extractHash();
705
-
706
- if (fundingOutputHash.length == 20) {
707
- // A 20-byte output hash is used by P2SH. That hash is constructed
708
- // by applying OP_HASH160 on the locking script. A 20-byte output
709
- // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
710
- // public key). However, since we compare the actual output hash
711
- // with an expected locking script hash, this check will succeed only
712
- // for P2SH transaction type with expected script hash value. For
713
- // P2PKH and P2WPKH, it will fail on the output hash comparison with
714
- // the expected locking script hash.
715
- require(
716
- fundingOutputHash.slice20(0) == expectedScript.hash160View(),
717
- "Wrong 20-byte script hash"
718
- );
719
- } else if (fundingOutputHash.length == 32) {
720
- // A 32-byte output hash is used by P2WSH. That hash is constructed
721
- // by applying OP_SHA256 on the locking script.
722
- require(
723
- fundingOutputHash.toBytes32() == sha256(expectedScript),
724
- "Wrong 32-byte script hash"
725
- );
726
- } else {
727
- revert("Wrong script hash length");
728
- }
729
-
730
- // Resulting TX hash is in native Bitcoin little-endian format.
731
- bytes32 fundingTxHash = abi
732
- .encodePacked(
733
- fundingTx.version,
734
- fundingTx.inputVector,
735
- fundingTx.outputVector,
736
- fundingTx.locktime
737
- )
738
- .hash256View();
739
-
740
- DepositRequest storage deposit = deposits[
741
- uint256(
742
- keccak256(
743
- abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)
744
- )
745
- )
746
- ];
747
- require(deposit.revealedAt == 0, "Deposit already revealed");
748
-
749
- uint64 fundingOutputAmount = fundingOutput.extractValue();
750
-
751
- require(
752
- fundingOutputAmount >= depositDustThreshold,
753
- "Deposit amount too small"
754
- );
755
-
756
- deposit.amount = fundingOutputAmount;
757
- deposit.depositor = reveal.depositor;
758
- /* solhint-disable-next-line not-rely-on-time */
759
- deposit.revealedAt = uint32(block.timestamp);
760
- deposit.vault = reveal.vault;
761
- deposit.treasuryFee = depositTreasuryFeeDivisor > 0
762
- ? fundingOutputAmount / depositTreasuryFeeDivisor
763
- : 0;
764
-
765
- emit DepositRevealed(
766
- fundingTxHash,
767
- reveal.fundingOutputIndex,
768
- reveal.depositor,
769
- fundingOutputAmount,
770
- reveal.blindingFactor,
771
- reveal.walletPubKeyHash,
772
- reveal.refundPubKeyHash,
773
- reveal.refundLocktime,
774
- reveal.vault
775
- );
606
+ self.revealDeposit(wallets, fundingTx, reveal);
776
607
  }
777
608
 
778
609
  /// @notice Used by the wallet to prove the BTC deposit sweep transaction
@@ -1055,7 +886,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1055
886
  uint256 inputLength
1056
887
  ) = parseTxInputAt(sweepTxInputVector, inputStartingIndex);
1057
888
 
1058
- DepositRequest storage deposit = deposits[
889
+ Deposit.Request storage deposit = self.deposits[
1059
890
  uint256(
1060
891
  keccak256(abi.encodePacked(outpointTxHash, outpointIndex))
1061
892
  )
@@ -1200,6 +1031,183 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1200
1031
  return (depositTxFee, depositTxFeeRemainder);
1201
1032
  }
1202
1033
 
1034
+ /// @notice Submits a fraud challenge indicating that a UTXO being under
1035
+ /// wallet control was unlocked by the wallet but was not used
1036
+ /// according to the protocol rules. That means the wallet signed
1037
+ /// a transaction input pointing to that UTXO and there is a unique
1038
+ /// sighash and signature pair associated with that input. This
1039
+ /// function uses those parameters to create a fraud accusation that
1040
+ /// proves a given transaction input unlocking the given UTXO was
1041
+ /// actually signed by the wallet. This function cannot determine
1042
+ /// whether the transaction was actually broadcast and the input was
1043
+ /// consumed in a fraudulent way so it just opens a challenge period
1044
+ /// during which the wallet can defeat the challenge by submitting
1045
+ /// proof of a transaction that consumes the given input according
1046
+ /// to protocol rules. To prevent spurious allegations, the caller
1047
+ /// must deposit ETH that is returned back upon justified fraud
1048
+ /// challenge or confiscated otherwise.
1049
+ ///@param walletPublicKey The public key of the wallet in the uncompressed
1050
+ /// and unprefixed format (64 bytes)
1051
+ /// @param sighash The hash that was used to produce the ECDSA signature
1052
+ /// that is the subject of the fraud claim. This hash is constructed
1053
+ /// by applying double SHA-256 over a serialized subset of the
1054
+ /// transaction. The exact subset used as hash preimage depends on
1055
+ /// the transaction input the signature is produced for. See BIP-143
1056
+ /// for reference
1057
+ /// @param signature Bitcoin signature in the R/S/V format
1058
+ /// @dev Requirements:
1059
+ /// - Wallet behind `walletPubKey` must be in `Live` or `MovingFunds`
1060
+ /// state
1061
+ /// - The challenger must send appropriate amount of ETH used as
1062
+ /// fraud challenge deposit
1063
+ /// - The signature (represented by r, s and v) must be generated by
1064
+ /// the wallet behind `walletPubKey` during signing of `sighash`
1065
+ /// - Wallet can be challenged for the given signature only once
1066
+ function submitFraudChallenge(
1067
+ bytes calldata walletPublicKey,
1068
+ bytes32 sighash,
1069
+ BitcoinTx.RSVSignature calldata signature
1070
+ ) external payable {
1071
+ bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
1072
+ walletPublicKey.slice32(0),
1073
+ walletPublicKey.slice32(32)
1074
+ );
1075
+ bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
1076
+
1077
+ Wallets.Wallet storage wallet = wallets.registeredWallets[
1078
+ walletPubKeyHash
1079
+ ];
1080
+
1081
+ require(
1082
+ wallet.state == Wallets.WalletState.Live ||
1083
+ wallet.state == Wallets.WalletState.MovingFunds,
1084
+ "Wallet is neither in Live nor MovingFunds state"
1085
+ );
1086
+
1087
+ frauds.submitChallenge(
1088
+ walletPublicKey,
1089
+ walletPubKeyHash,
1090
+ sighash,
1091
+ signature
1092
+ );
1093
+ }
1094
+
1095
+ /// @notice Allows to defeat a pending fraud challenge against a wallet if
1096
+ /// the transaction that spends the UTXO follows the protocol rules.
1097
+ /// In order to defeat the challenge the same `walletPublicKey` and
1098
+ /// signature (represented by `r`, `s` and `v`) must be provided as
1099
+ /// were used to calculate the sighash during input signing.
1100
+ /// The fraud challenge defeat attempt will only succeed if the
1101
+ /// inputs in the preimage are considered honestly spent by the
1102
+ /// wallet. Therefore the transaction spending the UTXO must be
1103
+ /// proven in the Bridge before a challenge defeat is called.
1104
+ /// If successfully defeated, the fraud challenge is marked as
1105
+ /// resolved and the amount of ether deposited by the challenger is
1106
+ /// sent to the treasury.
1107
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
1108
+ /// and unprefixed format (64 bytes)
1109
+ /// @param preimage The preimage which produces sighash used to generate the
1110
+ /// ECDSA signature that is the subject of the fraud claim. It is a
1111
+ /// serialized subset of the transaction. The exact subset used as
1112
+ /// the preimage depends on the transaction input the signature is
1113
+ /// produced for. See BIP-143 for reference
1114
+ /// @param witness Flag indicating whether the preimage was produced for a
1115
+ /// witness input. True for witness, false for non-witness input
1116
+ /// @dev Requirements:
1117
+ /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
1118
+ /// must identify an open fraud challenge
1119
+ /// - the preimage must be a valid preimage of a transaction generated
1120
+ /// according to the protocol rules and already proved in the Bridge
1121
+ /// - before a defeat attempt is made the transaction that spends the
1122
+ /// given UTXO must be proven in the Bridge
1123
+ function defeatFraudChallenge(
1124
+ bytes calldata walletPublicKey,
1125
+ bytes calldata preimage,
1126
+ bool witness
1127
+ ) external {
1128
+ uint256 utxoKey = frauds.unwrapChallenge(
1129
+ walletPublicKey,
1130
+ preimage,
1131
+ witness
1132
+ );
1133
+
1134
+ // Check that the UTXO key identifies a correctly spent UTXO.
1135
+ require(
1136
+ self.deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
1137
+ "Spent UTXO not found among correctly spent UTXOs"
1138
+ );
1139
+
1140
+ frauds.defeatChallenge(walletPublicKey, preimage, treasury);
1141
+ }
1142
+
1143
+ /// @notice Notifies about defeat timeout for the given fraud challenge.
1144
+ /// Can be called only if there was a fraud challenge identified by
1145
+ /// the provided `walletPublicKey` and `sighash` and it was not
1146
+ /// defeated on time. The amount of time that needs to pass after
1147
+ /// a fraud challenge is reported is indicated by the
1148
+ /// `challengeDefeatTimeout`. After a successful fraud challenge
1149
+ /// defeat timeout notification the fraud challenge is marked as
1150
+ /// resolved, the stake of each operator is slashed, the ether
1151
+ /// deposited is returned to the challenger and the challenger is
1152
+ /// rewarded.
1153
+ /// @param walletPublicKey The public key of the wallet in the uncompressed
1154
+ /// and unprefixed format (64 bytes)
1155
+ /// @param sighash The hash that was used to produce the ECDSA signature
1156
+ /// that is the subject of the fraud claim. This hash is constructed
1157
+ /// by applying double SHA-256 over a serialized subset of the
1158
+ /// transaction. The exact subset used as hash preimage depends on
1159
+ /// the transaction input the signature is produced for. See BIP-143
1160
+ /// for reference
1161
+ /// @dev Requirements:
1162
+ /// - `walletPublicKey`and `sighash` must identify an open fraud
1163
+ /// challenge
1164
+ /// - the amount of time indicated by `challengeDefeatTimeout` must
1165
+ /// pass after the challenge was reported
1166
+ function notifyFraudChallengeDefeatTimeout(
1167
+ bytes calldata walletPublicKey,
1168
+ bytes32 sighash
1169
+ ) external {
1170
+ frauds.notifyChallengeDefeatTimeout(walletPublicKey, sighash);
1171
+ }
1172
+
1173
+ /// @notice Returns parameters used by the `Frauds` library.
1174
+ /// @return slashingAmount Value of the slashing amount
1175
+ /// @return notifierRewardMultiplier Value of the notifier reward multiplier
1176
+ /// @return challengeDefeatTimeout Value of the challenge defeat timeout
1177
+ /// @return challengeDepositAmount Value of the challenge deposit amount
1178
+ function getFraudParameters()
1179
+ external
1180
+ view
1181
+ returns (
1182
+ uint256 slashingAmount,
1183
+ uint256 notifierRewardMultiplier,
1184
+ uint256 challengeDefeatTimeout,
1185
+ uint256 challengeDepositAmount
1186
+ )
1187
+ {
1188
+ slashingAmount = frauds.slashingAmount;
1189
+ notifierRewardMultiplier = frauds.notifierRewardMultiplier;
1190
+ challengeDefeatTimeout = frauds.challengeDefeatTimeout;
1191
+ challengeDepositAmount = frauds.challengeDepositAmount;
1192
+
1193
+ return (
1194
+ slashingAmount,
1195
+ notifierRewardMultiplier,
1196
+ challengeDefeatTimeout,
1197
+ challengeDepositAmount
1198
+ );
1199
+ }
1200
+
1201
+ /// @notice Returns the fraud challenge identified by the given key built
1202
+ /// as keccak256(walletPublicKey|sighash).
1203
+ function fraudChallenges(uint256 challengeKey)
1204
+ external
1205
+ view
1206
+ returns (Frauds.FraudChallenge memory)
1207
+ {
1208
+ return frauds.challenges[challengeKey];
1209
+ }
1210
+
1203
1211
  /// @notice Requests redemption of the given amount from the specified
1204
1212
  /// wallet to the redeemer Bitcoin output script.
1205
1213
  /// @param walletPubKeyHash The 20-byte wallet public key hash (computed
@@ -1418,9 +1426,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1418
1426
  proofDifficultyContext()
1419
1427
  );
1420
1428
 
1421
- // Perform validation of the redemption transaction input. Specifically,
1422
- // check if it refers to the expected wallet's main UTXO.
1423
- validateRedemptionTxInput(
1429
+ // Process the redemption transaction input. Specifically, check if it
1430
+ // refers to the expected wallet's main UTXO.
1431
+ processWalletOutboundTxInput(
1424
1432
  redemptionTx.inputVector,
1425
1433
  mainUtxo,
1426
1434
  walletPubKeyHash
@@ -1469,191 +1477,14 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1469
1477
  bank.transferBalance(treasury, outputsInfo.totalTreasuryFee);
1470
1478
  }
1471
1479
 
1472
- /// @notice Submits a fraud challenge indicating that a UTXO being under
1473
- /// wallet control was unlocked by the wallet but was not used
1474
- /// according to the protocol rules. That means the wallet signed
1475
- /// a transaction input pointing to that UTXO and there is a unique
1476
- /// sighash and signature pair associated with that input. This
1477
- /// function uses those parameters to create a fraud accusation that
1478
- /// proves a given transaction input unlocking the given UTXO was
1479
- /// actually signed by the wallet. This function cannot determine
1480
- /// whether the transaction was actually broadcast and the input was
1481
- /// consumed in a fraudulent way so it just opens a challenge period
1482
- /// during which the wallet can defeat the challenge by submitting
1483
- /// proof of a transaction that consumes the given input according
1484
- /// to protocol rules. To prevent spurious allegations, the caller
1485
- /// must deposit ETH that is returned back upon justified fraud
1486
- /// challenge or confiscated otherwise.
1487
- /// @param walletPublicKey The public key of the wallet in the uncompressed
1488
- /// and unprefixed format (64 bytes)
1489
- /// @param sighash The hash that was used to produce the ECDSA signature
1490
- /// that is the subject of the fraud claim. This hash is constructed
1491
- /// by applying double SHA-256 over a serialized subset of the
1492
- /// transaction. The exact subset used as hash preimage depends on
1493
- /// the transaction input the signature is produced for. See BIP-143
1494
- /// for reference
1495
- /// @param signature Bitcoin signature in the R/S/V format
1496
- /// @dev Requirements:
1497
- /// - Wallet behind `walletPubKey` must be in `Live` or `MovingFunds`
1498
- /// state
1499
- /// - The challenger must send appropriate amount of ETH used as
1500
- /// fraud challenge deposit
1501
- /// - The signature (represented by r, s and v) must be generated by
1502
- /// the wallet behind `walletPubKey` during signing of `sighash`
1503
- /// - Wallet can be challenged for the given signature only once
1504
- /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1505
- function submitFraudChallenge(
1506
- bytes calldata walletPublicKey,
1507
- bytes32 sighash,
1508
- BitcoinTx.RSVSignature calldata signature
1509
- ) external payable {
1510
- bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(
1511
- walletPublicKey.slice32(0),
1512
- walletPublicKey.slice32(32)
1513
- );
1514
- bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();
1515
-
1516
- Wallets.Wallet storage wallet = wallets.registeredWallets[
1517
- walletPubKeyHash
1518
- ];
1519
-
1520
- require(
1521
- wallet.state == Wallets.WalletState.Live ||
1522
- wallet.state == Wallets.WalletState.MovingFunds,
1523
- "Wallet is neither in Live nor MovingFunds state"
1524
- );
1525
-
1526
- frauds.submitFraudChallenge(
1527
- walletPublicKey,
1528
- walletPubKeyHash,
1529
- sighash,
1530
- signature
1531
- );
1532
- }
1533
-
1534
- /// @notice Allows to defeat a pending fraud challenge against a wallet if
1535
- /// the transaction that spends the UTXO follows the protocol rules.
1536
- /// In order to defeat the challenge the same `walletPublicKey` and
1537
- /// signature (represented by `r`, `s` and `v`) must be provided as
1538
- /// were used in the fraud challenge. Additionally a preimage must
1539
- /// be provided which was used to calculate the sighash during input
1540
- /// signing. The fraud challenge defeat attempt will only succeed if
1541
- /// the inputs in the preimage are considered honestly spent by the
1542
- /// wallet. Therefore the transaction spending the UTXO must be
1543
- /// proven in the Bridge before a challenge defeat is called.
1544
- /// If successfully defeated, the fraud challenge is marked as
1545
- /// resolved and the amount of ether deposited by the challenger is
1546
- /// sent to the treasury.
1547
- /// @param walletPublicKey The public key of the wallet in the uncompressed
1548
- /// and unprefixed format (64 bytes)
1549
- /// @param preimage The preimage which produces sighash used to generate the
1550
- /// ECDSA signature that is the subject of the fraud claim. It is a
1551
- /// serialized subset of the transaction. The exact subset used as
1552
- /// the preimage depends on the transaction input the signature is
1553
- /// produced for. See BIP-143 for reference
1554
- /// @param witness Flag indicating whether the preimage was produced for a
1555
- /// witness input. True for witness, false for non-witness input
1556
- /// @dev Requirements:
1557
- /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`
1558
- /// must identify an open fraud challenge
1559
- /// - the preimage must be a valid preimage of a transaction generated
1560
- /// according to the protocol rules and already proved in the Bridge
1561
- /// - before a defeat attempt is made the transaction that spends the
1562
- /// given UTXO must be proven in the Bridge
1563
- /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1564
- function defeatFraudChallenge(
1565
- bytes calldata walletPublicKey,
1566
- bytes calldata preimage,
1567
- bool witness
1568
- ) external {
1569
- uint256 utxoKey = frauds.unwrapChallenge(
1570
- walletPublicKey,
1571
- preimage,
1572
- witness
1573
- );
1574
-
1575
- // Check that the UTXO key identifies a correctly spent UTXO.
1576
- require(
1577
- deposits[utxoKey].sweptAt > 0 || spentMainUTXOs[utxoKey],
1578
- "Spent UTXO not found among correctly spent UTXOs"
1579
- );
1580
-
1581
- frauds.defeatChallenge(walletPublicKey, preimage, treasury);
1582
- }
1583
-
1584
- /// @notice Notifies about defeat timeout for the given fraud challenge.
1585
- /// Can be called only if there was a fraud challenge identified by
1586
- /// the provided `walletPublicKey` and `sighash` and it was not
1587
- /// defeated on time. The amount of time that needs to pass after
1588
- /// a fraud challenge is reported is indicated by the
1589
- /// `challengeDefeatTimeout`. After a successful fraud challenge
1590
- /// defeat timeout notification the fraud challenge is marked as
1591
- /// resolved, the stake of each operator is slashed, the ether
1592
- /// deposited is returned to the challenger and the challenger is
1593
- /// rewarded.
1594
- /// @param walletPublicKey The public key of the wallet in the uncompressed
1595
- /// and unprefixed format (64 bytes)
1596
- /// @param sighash The hash that was used to produce the ECDSA signature
1597
- /// that is the subject of the fraud claim. This hash is constructed
1598
- /// by applying double SHA-256 over a serialized subset of the
1599
- /// transaction. The exact subset used as hash preimage depends on
1600
- /// the transaction input the signature is produced for. See BIP-143
1601
- /// for reference
1602
- /// @dev Requirements:
1603
- /// - `walletPublicKey`and `sighash` must identify an open fraud
1604
- /// challenge
1605
- /// - the amount of time indicated by `challengeDefeatTimeout` must
1606
- /// pass after the challenge was reported
1607
- /// TODO: Consider using wallet public key in the X/Y form to avoid slicing.
1608
- function notifyFraudChallengeDefeatTimeout(
1609
- bytes calldata walletPublicKey,
1610
- bytes32 sighash
1611
- ) external {
1612
- frauds.notifyFraudChallengeDefeatTimeout(walletPublicKey, sighash);
1613
- }
1614
-
1615
- /// @notice Returns parameters used by the `Frauds` library.
1616
- /// @return slashingAmount Value of the slashing amount
1617
- /// @return notifierRewardMultiplier Value of the notifier reward multiplier
1618
- /// @return challengeDefeatTimeout Value of the challenge defeat timeout
1619
- /// @return challengeDepositAmount Value of the challenge deposit amount
1620
- function getFraudParameters()
1621
- external
1622
- view
1623
- returns (
1624
- uint256 slashingAmount,
1625
- uint256 notifierRewardMultiplier,
1626
- uint256 challengeDefeatTimeout,
1627
- uint256 challengeDepositAmount
1628
- )
1629
- {
1630
- slashingAmount = frauds.slashingAmount;
1631
- notifierRewardMultiplier = frauds.notifierRewardMultiplier;
1632
- challengeDefeatTimeout = frauds.challengeDefeatTimeout;
1633
- challengeDepositAmount = frauds.challengeDepositAmount;
1634
-
1635
- return (
1636
- slashingAmount,
1637
- notifierRewardMultiplier,
1638
- challengeDefeatTimeout,
1639
- challengeDepositAmount
1640
- );
1641
- }
1642
-
1643
- /// @notice Returns the fraud challenge identified by the given key built
1644
- /// as keccak256(walletPublicKey|sighash|v|r|s).
1645
- function fraudChallenges(uint256 challengeKey)
1646
- external
1647
- view
1648
- returns (Frauds.FraudChallenge memory)
1649
- {
1650
- return frauds.challenges[challengeKey];
1651
- }
1652
-
1653
- /// @notice Validates whether the redemption Bitcoin transaction input
1654
- /// vector contains a single input referring to the wallet's main
1655
- /// UTXO. Reverts in case the validation fails.
1656
- /// @param redemptionTxInputVector Bitcoin redemption transaction input
1480
+ /// @notice Checks whether an outbound Bitcoin transaction performed from
1481
+ /// the given wallet has an input vector that contains a single
1482
+ /// input referring to the wallet's main UTXO. Marks that main UTXO
1483
+ /// as correctly spent if the validation succeeds. Reverts otherwise.
1484
+ /// There are two outbound transactions from a wallet possible: a
1485
+ /// redemption transaction or a moving funds to another wallet
1486
+ /// transaction.
1487
+ /// @param walletOutboundTxInputVector Bitcoin outbound transaction's input
1657
1488
  /// vector. This function assumes vector's structure is valid so it
1658
1489
  /// must be validated using e.g. `BTCUtils.validateVin` function
1659
1490
  /// before it is passed here
@@ -1661,9 +1492,9 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1661
1492
  /// the Ethereum chain.
1662
1493
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1663
1494
  // HASH160 over the compressed ECDSA public key) of the wallet which
1664
- /// performed the redemption transaction.
1665
- function validateRedemptionTxInput(
1666
- bytes memory redemptionTxInputVector,
1495
+ /// performed the outbound transaction.
1496
+ function processWalletOutboundTxInput(
1497
+ bytes memory walletOutboundTxInputVector,
1667
1498
  BitcoinTx.UTXO calldata mainUtxo,
1668
1499
  bytes20 walletPubKeyHash
1669
1500
  ) internal {
@@ -1686,16 +1517,16 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1686
1517
  "Invalid main UTXO data"
1687
1518
  );
1688
1519
 
1689
- // Assert that the single redemption transaction input actually
1520
+ // Assert that the single outbound transaction input actually
1690
1521
  // refers to the wallet's main UTXO.
1691
1522
  (
1692
- bytes32 redemptionTxOutpointTxHash,
1693
- uint32 redemptionTxOutpointIndex
1694
- ) = processRedemptionTxInput(redemptionTxInputVector);
1523
+ bytes32 outpointTxHash,
1524
+ uint32 outpointIndex
1525
+ ) = parseWalletOutboundTxInput(walletOutboundTxInputVector);
1695
1526
  require(
1696
- mainUtxo.txHash == redemptionTxOutpointTxHash &&
1697
- mainUtxo.txOutputIndex == redemptionTxOutpointIndex,
1698
- "Redemption transaction input must point to the wallet's main UTXO"
1527
+ mainUtxo.txHash == outpointTxHash &&
1528
+ mainUtxo.txOutputIndex == outpointIndex,
1529
+ "Outbound transaction input must point to the wallet's main UTXO"
1699
1530
  );
1700
1531
 
1701
1532
  // Main UTXO used as an input, mark it as spent.
@@ -1708,10 +1539,13 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1708
1539
  ] = true;
1709
1540
  }
1710
1541
 
1711
- /// @notice Processes the Bitcoin redemption transaction input vector. It
1712
- /// extracts the single input then the transaction hash and output
1713
- /// index from its outpoint.
1714
- /// @param redemptionTxInputVector Bitcoin redemption transaction input
1542
+ /// @notice Parses the input vector of an outbound Bitcoin transaction
1543
+ /// performed from the given wallet. It extracts the single input
1544
+ /// then the transaction hash and output index from its outpoint.
1545
+ /// There are two outbound transactions from a wallet possible: a
1546
+ /// redemption transaction or a moving funds to another wallet
1547
+ /// transaction.
1548
+ /// @param walletOutboundTxInputVector Bitcoin outbound transaction input
1715
1549
  /// vector. This function assumes vector's structure is valid so it
1716
1550
  /// must be validated using e.g. `BTCUtils.validateVin` function
1717
1551
  /// before it is passed here
@@ -1719,12 +1553,10 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1719
1553
  /// pointed in the input's outpoint.
1720
1554
  /// @return outpointIndex 4-byte index of the Bitcoin transaction output
1721
1555
  /// which is pointed in the input's outpoint.
1722
- function processRedemptionTxInput(bytes memory redemptionTxInputVector)
1723
- internal
1724
- pure
1725
- returns (bytes32 outpointTxHash, uint32 outpointIndex)
1726
- {
1727
- // To determine the total number of redemption transaction inputs,
1556
+ function parseWalletOutboundTxInput(
1557
+ bytes memory walletOutboundTxInputVector
1558
+ ) internal pure returns (bytes32 outpointTxHash, uint32 outpointIndex) {
1559
+ // To determine the total number of Bitcoin transaction inputs,
1728
1560
  // we need to parse the compactSize uint (VarInt) the input vector is
1729
1561
  // prepended by. That compactSize uint encodes the number of vector
1730
1562
  // elements using the format presented in:
@@ -1732,13 +1564,13 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1732
1564
  // We don't need asserting the compactSize uint is parseable since it
1733
1565
  // was already checked during `validateVin` validation.
1734
1566
  // See `BitcoinTx.inputVector` docs for more details.
1735
- (, uint256 inputsCount) = redemptionTxInputVector.parseVarInt();
1567
+ (, uint256 inputsCount) = walletOutboundTxInputVector.parseVarInt();
1736
1568
  require(
1737
1569
  inputsCount == 1,
1738
- "Redemption transaction must have a single input"
1570
+ "Outbound transaction must have a single input"
1739
1571
  );
1740
1572
 
1741
- bytes memory input = redemptionTxInputVector.extractInputAtIndex(0);
1573
+ bytes memory input = walletOutboundTxInputVector.extractInputAtIndex(0);
1742
1574
 
1743
1575
  outpointTxHash = input.extractInputTxIdLE();
1744
1576
 
@@ -1801,11 +1633,26 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1801
1633
  // scripts that can be used to lock the change. This is done upfront to
1802
1634
  // save on gas. Both scripts have a strict format defined by Bitcoin.
1803
1635
  //
1804
- // The P2PKH script has format <0x1976a914> <20-byte PKH> <0x88ac>.
1636
+ // The P2PKH script has the byte format: <0x1976a914> <20-byte PKH> <0x88ac>.
1637
+ // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
1638
+ // - 0x19: Byte length of the entire script
1639
+ // - 0x76: OP_DUP
1640
+ // - 0xa9: OP_HASH160
1641
+ // - 0x14: Byte length of the public key hash
1642
+ // - 0x88: OP_EQUALVERIFY
1643
+ // - 0xac: OP_CHECKSIG
1644
+ // which matches the P2PKH structure as per:
1645
+ // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
1805
1646
  bytes32 walletP2PKHScriptKeccak = keccak256(
1806
1647
  abi.encodePacked(hex"1976a914", walletPubKeyHash, hex"88ac")
1807
1648
  );
1808
- // The P2WPKH script has format <0x160014> <20-byte PKH>.
1649
+ // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.
1650
+ // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
1651
+ // - 0x16: Byte length of the entire script
1652
+ // - 0x00: OP_0
1653
+ // - 0x14: Byte length of the public key hash
1654
+ // which matches the P2WPKH structure as per:
1655
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
1809
1656
  bytes32 walletP2WPKHScriptKeccak = keccak256(
1810
1657
  abi.encodePacked(hex"160014", walletPubKeyHash)
1811
1658
  );
@@ -1831,7 +1678,7 @@ contract Bridge is Ownable, EcdsaWalletOwner {
1831
1678
  // Extract the value from given output.
1832
1679
  uint64 outputValue = output.extractValue();
1833
1680
  // The output consists of an 8-byte value and a variable length
1834
- // script. To extract that script we slice the output staring from
1681
+ // script. To extract that script we slice the output starting from
1835
1682
  // 9th byte until the end.
1836
1683
  bytes memory outputScript = output.slice(8, output.length - 8);
1837
1684
 
@@ -2010,4 +1857,292 @@ contract Bridge is Ownable, EcdsaWalletOwner {
2010
1857
  // Return the requested amount of tokens to the redeemer
2011
1858
  bank.transferBalance(request.redeemer, request.requestedAmount);
2012
1859
  }
1860
+
1861
+ /// @notice Used by the wallet to prove the BTC moving funds transaction
1862
+ /// and to make the necessary state changes. Moving funds is only
1863
+ /// accepted if it satisfies SPV proof.
1864
+ ///
1865
+ /// The function validates the moving funds transaction structure
1866
+ /// by checking if it actually spends the main UTXO of the declared
1867
+ /// wallet and locks the value on the pre-committed target wallets
1868
+ /// using a reasonable transaction fee. If all preconditions are
1869
+ /// met, this functions closes the source wallet.
1870
+ ///
1871
+ /// It is possible to prove the given moving funds transaction only
1872
+ /// one time.
1873
+ /// @param movingFundsTx Bitcoin moving funds transaction data
1874
+ /// @param movingFundsProof Bitcoin moving funds proof data
1875
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
1876
+ /// the Ethereum chain
1877
+ /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
1878
+ /// HASH160 over the compressed ECDSA public key) of the wallet
1879
+ /// which performed the moving funds transaction
1880
+ /// @dev Requirements:
1881
+ /// - `movingFundsTx` components must match the expected structure. See
1882
+ /// `BitcoinTx.Info` docs for reference. Their values must exactly
1883
+ /// correspond to appropriate Bitcoin transaction fields to produce
1884
+ /// a provable transaction hash.
1885
+ /// - The `movingFundsTx` should represent a Bitcoin transaction with
1886
+ /// exactly 1 input that refers to the wallet's main UTXO. That
1887
+ /// transaction should have 1..n outputs corresponding to the
1888
+ /// pre-committed target wallets. Outputs must be ordered in the
1889
+ /// same way as their corresponding target wallets are ordered
1890
+ /// within the target wallets commitment.
1891
+ /// - `movingFundsProof` components must match the expected structure.
1892
+ /// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
1893
+ /// field must contain a valid number of block headers, not less
1894
+ /// than the `txProofDifficultyFactor` contract constant.
1895
+ /// - `mainUtxo` components must point to the recent main UTXO
1896
+ /// of the given wallet, as currently known on the Ethereum chain.
1897
+ /// Additionally, the recent main UTXO on Ethereum must be set.
1898
+ /// - `walletPubKeyHash` must be connected with the main UTXO used
1899
+ /// as transaction single input.
1900
+ /// - The wallet that `walletPubKeyHash` points to must be in the
1901
+ /// MovingFunds state.
1902
+ /// - The target wallets commitment must be submitted by the wallet
1903
+ /// that `walletPubKeyHash` points to.
1904
+ /// - The total Bitcoin transaction fee must be lesser or equal
1905
+ /// to `movingFundsTxMaxTotalFee` governable parameter.
1906
+ function submitMovingFundsProof(
1907
+ BitcoinTx.Info calldata movingFundsTx,
1908
+ BitcoinTx.Proof calldata movingFundsProof,
1909
+ BitcoinTx.UTXO calldata mainUtxo,
1910
+ bytes20 walletPubKeyHash
1911
+ ) external {
1912
+ // The actual transaction proof is performed here. After that point, we
1913
+ // can assume the transaction happened on Bitcoin chain and has
1914
+ // a sufficient number of confirmations as determined by
1915
+ // `txProofDifficultyFactor` constant.
1916
+ bytes32 movingFundsTxHash = BitcoinTx.validateProof(
1917
+ movingFundsTx,
1918
+ movingFundsProof,
1919
+ proofDifficultyContext()
1920
+ );
1921
+
1922
+ // Process the moving funds transaction input. Specifically, check if
1923
+ // it refers to the expected wallet's main UTXO.
1924
+ processWalletOutboundTxInput(
1925
+ movingFundsTx.inputVector,
1926
+ mainUtxo,
1927
+ walletPubKeyHash
1928
+ );
1929
+
1930
+ (
1931
+ bytes32 targetWalletsHash,
1932
+ uint256 outputsTotalValue
1933
+ ) = processMovingFundsTxOutputs(movingFundsTx.outputVector);
1934
+
1935
+ require(
1936
+ mainUtxo.txOutputValue - outputsTotalValue <=
1937
+ movingFundsTxMaxTotalFee,
1938
+ "Transaction fee is too high"
1939
+ );
1940
+
1941
+ wallets.notifyFundsMoved(walletPubKeyHash, targetWalletsHash);
1942
+
1943
+ emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
1944
+ }
1945
+
1946
+ /// @notice Processes the moving funds Bitcoin transaction output vector
1947
+ /// and extracts information required for further processing.
1948
+ /// @param movingFundsTxOutputVector Bitcoin moving funds transaction output
1949
+ /// vector. This function assumes vector's structure is valid so it
1950
+ /// must be validated using e.g. `BTCUtils.validateVout` function
1951
+ /// before it is passed here
1952
+ /// @return targetWalletsHash keccak256 hash over the list of actual
1953
+ /// target wallets used in the transaction.
1954
+ /// @return outputsTotalValue Sum of all outputs values.
1955
+ /// @dev Requirements:
1956
+ /// - The `movingFundsTxOutputVector` must be parseable, i.e. must
1957
+ /// be validated by the caller as stated in their parameter doc.
1958
+ /// - Each output must refer to a 20-byte public key hash.
1959
+ /// - The total outputs value must be evenly divided over all outputs.
1960
+ function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
1961
+ internal
1962
+ view
1963
+ returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
1964
+ {
1965
+ // Determining the total number of Bitcoin transaction outputs in
1966
+ // the same way as for number of inputs. See `BitcoinTx.outputVector`
1967
+ // docs for more details.
1968
+ (
1969
+ uint256 outputsCompactSizeUintLength,
1970
+ uint256 outputsCount
1971
+ ) = movingFundsTxOutputVector.parseVarInt();
1972
+
1973
+ // To determine the first output starting index, we must jump over
1974
+ // the compactSize uint which prepends the output vector. One byte
1975
+ // must be added because `BtcUtils.parseVarInt` does not include
1976
+ // compactSize uint tag in the returned length.
1977
+ //
1978
+ // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
1979
+ // returns `0`, so we jump over one byte of compactSize uint.
1980
+ //
1981
+ // For >= 253 && <= 0xffff there is `0xfd` tag,
1982
+ // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
1983
+ // tag byte included) so we need to jump over 1+2 bytes of
1984
+ // compactSize uint.
1985
+ //
1986
+ // Please refer `BTCUtils` library and compactSize uint
1987
+ // docs in `BitcoinTx` library for more details.
1988
+ uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
1989
+
1990
+ bytes20[] memory targetWallets = new bytes20[](outputsCount);
1991
+ uint64[] memory outputsValues = new uint64[](outputsCount);
1992
+
1993
+ // Outputs processing loop.
1994
+ for (uint256 i = 0; i < outputsCount; i++) {
1995
+ uint256 outputLength = movingFundsTxOutputVector
1996
+ .determineOutputLengthAt(outputStartingIndex);
1997
+
1998
+ bytes memory output = movingFundsTxOutputVector.slice(
1999
+ outputStartingIndex,
2000
+ outputLength
2001
+ );
2002
+
2003
+ // Extract the output script payload.
2004
+ bytes memory targetWalletPubKeyHashBytes = output.extractHash();
2005
+ // Output script payload must refer to a known wallet public key
2006
+ // hash which is always 20-byte.
2007
+ require(
2008
+ targetWalletPubKeyHashBytes.length == 20,
2009
+ "Target wallet public key hash must have 20 bytes"
2010
+ );
2011
+
2012
+ bytes20 targetWalletPubKeyHash = targetWalletPubKeyHashBytes
2013
+ .slice20(0);
2014
+
2015
+ // The next step is making sure that the 20-byte public key hash
2016
+ // is actually used in the right context of a P2PKH or P2WPKH
2017
+ // output. To do so, we must extract the full script from the output
2018
+ // and compare with the expected P2PKH and P2WPKH scripts
2019
+ // referring to that 20-byte public key hash. The output consists
2020
+ // of an 8-byte value and a variable length script. To extract the
2021
+ // script we slice the output starting from 9th byte until the end.
2022
+ bytes32 outputScriptKeccak = keccak256(
2023
+ output.slice(8, output.length - 8)
2024
+ );
2025
+ // Build the expected P2PKH script which has the following byte
2026
+ // format: <0x1976a914> <20-byte PKH> <0x88ac>. According to
2027
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
2028
+ // - 0x19: Byte length of the entire script
2029
+ // - 0x76: OP_DUP
2030
+ // - 0xa9: OP_HASH160
2031
+ // - 0x14: Byte length of the public key hash
2032
+ // - 0x88: OP_EQUALVERIFY
2033
+ // - 0xac: OP_CHECKSIG
2034
+ // which matches the P2PKH structure as per:
2035
+ // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
2036
+ bytes32 targetWalletP2PKHScriptKeccak = keccak256(
2037
+ abi.encodePacked(
2038
+ hex"1976a914",
2039
+ targetWalletPubKeyHash,
2040
+ hex"88ac"
2041
+ )
2042
+ );
2043
+ // Build the expected P2WPKH script which has the following format:
2044
+ // <0x160014> <20-byte PKH>. According to
2045
+ // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
2046
+ // - 0x16: Byte length of the entire script
2047
+ // - 0x00: OP_0
2048
+ // - 0x14: Byte length of the public key hash
2049
+ // which matches the P2WPKH structure as per:
2050
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
2051
+ bytes32 targetWalletP2WPKHScriptKeccak = keccak256(
2052
+ abi.encodePacked(hex"160014", targetWalletPubKeyHash)
2053
+ );
2054
+ // Make sure the actual output script matches either the P2PKH
2055
+ // or P2WPKH format.
2056
+ require(
2057
+ outputScriptKeccak == targetWalletP2PKHScriptKeccak ||
2058
+ outputScriptKeccak == targetWalletP2WPKHScriptKeccak,
2059
+ "Output must be P2PKH or P2WPKH"
2060
+ );
2061
+
2062
+ // Add the wallet public key hash to the list that will be used
2063
+ // to build the result list hash. There is no need to check if
2064
+ // given output is a change here because the actual target wallet
2065
+ // list must be exactly the same as the pre-committed target wallet
2066
+ // list which is guaranteed to be valid.
2067
+ targetWallets[i] = targetWalletPubKeyHash;
2068
+
2069
+ // Extract the value from given output.
2070
+ outputsValues[i] = output.extractValue();
2071
+ outputsTotalValue += outputsValues[i];
2072
+
2073
+ // Make the `outputStartingIndex` pointing to the next output by
2074
+ // increasing it by current output's length.
2075
+ outputStartingIndex += outputLength;
2076
+ }
2077
+
2078
+ // Compute the indivisible remainder that remains after dividing the
2079
+ // outputs total value over all outputs evenly.
2080
+ uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
2081
+ // Compute the minimum allowed output value by dividing the outputs
2082
+ // total value (reduced by the remainder) by the number of outputs.
2083
+ uint256 minOutputValue = (outputsTotalValue -
2084
+ outputsTotalValueRemainder) / outputsCount;
2085
+ // Maximum possible value is the minimum value with the remainder included.
2086
+ uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
2087
+
2088
+ for (uint256 i = 0; i < outputsCount; i++) {
2089
+ require(
2090
+ minOutputValue <= outputsValues[i] &&
2091
+ outputsValues[i] <= maxOutputValue,
2092
+ "Transaction amount is not distributed evenly"
2093
+ );
2094
+ }
2095
+
2096
+ targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
2097
+
2098
+ return (targetWalletsHash, outputsTotalValue);
2099
+ }
2100
+
2101
+ /// @notice Returns the current values of Bridge deposit parameters.
2102
+ /// @return depositDustThreshold The minimal amount that can be requested
2103
+ /// to deposit. Value of this parameter must take into account the
2104
+ /// value of `depositTreasuryFeeDivisor` and `depositTxMaxFee`
2105
+ /// parameters in order to make requests that can incur the
2106
+ /// treasury and transaction fee and still satisfy the depositor.
2107
+ /// @return depositTreasuryFeeDivisor Divisor used to compute the treasury
2108
+ /// fee taken from each deposit and transferred to the treasury upon
2109
+ /// sweep proof submission. That fee is computed as follows:
2110
+ /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`
2111
+ /// For example, if the treasury fee needs to be 2% of each deposit,
2112
+ /// the `depositTreasuryFeeDivisor` should be set to `50`
2113
+ /// because `1/50 = 0.02 = 2%`.
2114
+ function depositParameters()
2115
+ external
2116
+ view
2117
+ returns (uint64 depositDustThreshold, uint64 depositTreasuryFeeDivisor)
2118
+ {
2119
+ depositDustThreshold = self.depositDustThreshold;
2120
+ depositTreasuryFeeDivisor = self.depositTreasuryFeeDivisor;
2121
+ }
2122
+
2123
+ /// @notice Indicates if the vault with the given address is trusted or not.
2124
+ /// Depositors can route their revealed deposits only to trusted
2125
+ /// vaults and have trusted vaults notified about new deposits as
2126
+ /// soon as these deposits get swept. Vaults not trusted by the
2127
+ /// Bridge can still be used by Bank balance owners on their own
2128
+ /// responsibility - anyone can approve their Bank balance to any
2129
+ /// address.
2130
+ function isVaultTrusted(address vault) external view returns (bool) {
2131
+ return self.isVaultTrusted[vault];
2132
+ }
2133
+
2134
+ /// @notice Collection of all revealed deposits indexed by
2135
+ /// keccak256(fundingTxHash | fundingOutputIndex).
2136
+ /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)
2137
+ /// and fundingOutputIndex an uint32. This mapping may contain valid
2138
+ /// and invalid deposits and the wallet is responsible for
2139
+ /// validating them before attempting to execute a sweep.
2140
+ function deposits(uint256 depositKey)
2141
+ external
2142
+ view
2143
+ returns (Deposit.Request memory)
2144
+ {
2145
+ // TODO: rename to getDeposit?
2146
+ return self.deposits[depositKey];
2147
+ }
2013
2148
  }