@keep-network/tbtc-v2 0.1.1-dev.21 → 0.1.1-dev.24

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 (26) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/TBTC.json +11 -10
  3. package/artifacts/TBTCToken.json +11 -10
  4. package/artifacts/VendingMachine.json +12 -11
  5. package/artifacts/solcInputs/2676c70e1dffa939dbf0519ef3304b34.json +191 -0
  6. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  7. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  8. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  9. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +89 -3
  10. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  11. package/build/contracts/bridge/Bridge.sol/Bridge.json +337 -27
  12. package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
  13. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  14. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  15. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  16. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  17. package/build/contracts/bridge/Wallets.sol/Wallets.json +87 -0
  18. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  19. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  20. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  21. package/contracts/bridge/BitcoinTx.sol +107 -2
  22. package/contracts/bridge/Bridge.sol +212 -181
  23. package/contracts/bridge/EcdsaLib.sol +30 -0
  24. package/contracts/bridge/Wallets.sol +352 -0
  25. package/package.json +21 -17
  26. package/artifacts/solcInputs/af7a1ba64c125a6b01a6d1518b2bcc34.json +0 -140
@@ -19,10 +19,12 @@ import "@openzeppelin/contracts/access/Ownable.sol";
19
19
 
20
20
  import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
21
21
  import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
22
- import {ValidateSPV} from "@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol";
22
+ import {IWalletOwner as EcdsaWalletOwner} from "@keep-network/ecdsa/contracts/api/IWalletOwner.sol";
23
23
 
24
24
  import "../bank/Bank.sol";
25
25
  import "./BitcoinTx.sol";
26
+ import "./EcdsaLib.sol";
27
+ import "./Wallets.sol";
26
28
 
27
29
  /// @title Interface for the Bitcoin relay
28
30
  /// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay
@@ -55,12 +57,11 @@ interface IRelay {
55
57
  /// wallet informs the Bridge about the sweep increasing appropriate
56
58
  /// balances in the Bank.
57
59
  /// @dev Bridge is an upgradeable component of the Bank.
58
- contract Bridge is Ownable {
60
+ contract Bridge is Ownable, EcdsaWalletOwner {
59
61
  using BTCUtils for bytes;
60
62
  using BTCUtils for uint256;
61
63
  using BytesLib for bytes;
62
- using ValidateSPV for bytes;
63
- using ValidateSPV for bytes32;
64
+ using Wallets for Wallets.Data;
64
65
 
65
66
  /// @notice Represents data which must be revealed by the depositor during
66
67
  /// deposit reveal.
@@ -168,36 +169,6 @@ contract Bridge is Ownable {
168
169
  uint64 changeValue;
169
170
  }
170
171
 
171
- /// @notice Represents wallet state:
172
- enum WalletState {
173
- /// @dev The wallet is unknown to the Bridge.
174
- Unknown,
175
- /// @dev The wallet can sweep deposits and accept redemption requests.
176
- Active,
177
- /// @dev The wallet was deemed unhealthy and is expected to move their
178
- /// outstanding funds to another wallet. The wallet can still
179
- /// fulfill their pending redemption requests although new
180
- /// redemption requests and new deposit reveals are not accepted.
181
- MovingFunds,
182
- /// @dev The wallet moved or redeemed all their funds and cannot
183
- /// perform any action.
184
- Closed,
185
- /// @dev The wallet committed a fraud that was reported. The wallet is
186
- /// blocked and can not perform any actions in the Bridge.
187
- /// Off-chain coordination with the wallet operators is needed to
188
- /// recover funds.
189
- Terminated
190
- }
191
-
192
- /// @notice Holds information about a wallet.
193
- struct Wallet {
194
- // Current state of the wallet.
195
- WalletState state;
196
- // The total redeemable value of pending redemption requests targeting
197
- // that wallet.
198
- uint64 pendingRedemptionsValue;
199
- }
200
-
201
172
  /// @notice The number of confirmations on the Bitcoin chain required to
202
173
  /// successfully evaluate an SPV proof.
203
174
  uint256 public immutable txProofDifficultyFactor;
@@ -215,13 +186,21 @@ contract Bridge is Ownable {
215
186
  /// Treasury takes part in the operators rewarding process.
216
187
  address public immutable treasury;
217
188
 
189
+ /// TODO: Make it governable.
190
+ /// @notice The minimal amount that can be requested for deposit.
191
+ /// Value of this parameter must take into account the value of
192
+ /// `depositTreasuryFeeDivisor` and `depositTxMaxFee`
193
+ /// parameters in order to make requests that can incur the
194
+ /// treasury and transaction fee and still satisfy the depositor.
195
+ uint64 public depositDustThreshold;
196
+
218
197
  /// TODO: Make it governable.
219
198
  /// @notice Divisor used to compute the treasury fee taken from each
220
199
  /// deposit and transferred to the treasury upon sweep proof
221
200
  /// submission. That fee is computed as follows:
222
201
  /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`
223
202
  /// For example, if the treasury fee needs to be 2% of each deposit,
224
- /// the `redemptionTreasuryFeeDivisor` should be set to `50`
203
+ /// the `depositTreasuryFeeDivisor` should be set to `50`
225
204
  /// because `1/50 = 0.02 = 2%`.
226
205
  uint64 public depositTreasuryFeeDivisor;
227
206
 
@@ -282,15 +261,6 @@ contract Bridge is Ownable {
282
261
  /// validating them before attempting to execute a sweep.
283
262
  mapping(uint256 => DepositRequest) public deposits;
284
263
 
285
- /// @notice Maps the 20-byte wallet public key hash (computed using
286
- /// Bitcoin HASH160 over the compressed ECDSA public key) to
287
- /// the latest wallet's main UTXO computed as
288
- /// keccak256(txHash | txOutputIndex | txOutputValue). The `tx`
289
- /// prefix refers to the transaction which created that main UTXO.
290
- /// The txHash is bytes32 (ordered as in Bitcoin internally),
291
- /// txOutputIndex an uint32, and txOutputValue an uint64 value.
292
- mapping(bytes20 => bytes32) public mainUtxos;
293
-
294
264
  /// @notice Collection of all pending redemption requests indexed by
295
265
  /// redemption key built as
296
266
  /// keccak256(walletPubKeyHash | redeemerOutputScript). The
@@ -327,14 +297,27 @@ contract Bridge is Ownable {
327
297
  // slither-disable-next-line uninitialized-state
328
298
  mapping(uint256 => RedemptionRequest) public timedOutRedemptions;
329
299
 
330
- /// @notice Maps the 20-byte wallet public key hash (computed using
331
- /// Bitcoin HASH160 over the compressed ECDSA public key) to the
332
- /// basic wallet information like state and pending
333
- /// redemptions value.
334
- ///
335
- // TODO: Remove that Slither disable once this variable is used.
336
- // slither-disable-next-line uninitialized-state
337
- mapping(bytes20 => Wallet) public wallets;
300
+ /// @notice State related with wallets.
301
+ Wallets.Data internal wallets;
302
+
303
+ event WalletCreationPeriodUpdated(uint32 newCreationPeriod);
304
+
305
+ event WalletBtcBalanceRangeUpdated(
306
+ uint64 newMinBtcBalance,
307
+ uint64 newMaxBtcBalance
308
+ );
309
+
310
+ event NewWalletRequested();
311
+
312
+ event NewWalletRegistered(
313
+ bytes32 indexed ecdsaWalletID,
314
+ bytes20 indexed walletPubKeyHash
315
+ );
316
+
317
+ event WalletTerminated(
318
+ bytes32 indexed ecdsaWalletID,
319
+ bytes20 indexed walletPubKeyHash
320
+ );
338
321
 
339
322
  event VaultStatusUpdated(address indexed vault, bool isTrusted);
340
323
 
@@ -370,6 +353,7 @@ contract Bridge is Ownable {
370
353
  address _bank,
371
354
  address _relay,
372
355
  address _treasury,
356
+ address _ecdsaWalletRegistry,
373
357
  uint256 _txProofDifficultyFactor
374
358
  ) {
375
359
  require(_bank != address(0), "Bank address cannot be zero");
@@ -384,16 +368,55 @@ contract Bridge is Ownable {
384
368
  txProofDifficultyFactor = _txProofDifficultyFactor;
385
369
 
386
370
  // TODO: Revisit initial values.
371
+ depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
387
372
  depositTxMaxFee = 1000; // 1000 satoshi
388
373
  depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
389
374
  redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC
390
375
  redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
391
376
  redemptionTxMaxFee = 1000; // 1000 satoshi
392
377
  redemptionTimeout = 172800; // 48 hours
378
+
379
+ // TODO: Revisit initial values.
380
+ wallets.init(_ecdsaWalletRegistry);
381
+ wallets.setCreationPeriod(1 weeks);
382
+ wallets.setBtcBalanceRange(1 * 1e8, 10 * 1e8); // [1 BTC, 10 BTC]
393
383
  }
394
384
 
395
- // TODO: Add function `onNewWalletCreated` according to discussion:
396
- // https://github.com/keep-network/tbtc-v2/pull/128#discussion_r809885230
385
+ /// @notice Updates parameters used by the `Wallets` library.
386
+ /// @param creationPeriod New value of the wallet creation period
387
+ /// @param minBtcBalance New value of the minimum BTC balance
388
+ /// @param maxBtcBalance New value of the maximum BTC balance
389
+ /// @dev Requirements:
390
+ /// - Caller must be the contract owner.
391
+ /// - Minimum BTC balance must be greater than zero
392
+ /// - Maximum BTC balance must be greater than minimum BTC balance
393
+ function updateWalletsParameters(
394
+ uint32 creationPeriod,
395
+ uint64 minBtcBalance,
396
+ uint64 maxBtcBalance
397
+ ) external onlyOwner {
398
+ wallets.setCreationPeriod(creationPeriod);
399
+ wallets.setBtcBalanceRange(minBtcBalance, maxBtcBalance);
400
+ }
401
+
402
+ /// @return creationPeriod Value of the wallet creation period
403
+ /// @return minBtcBalance Value of the minimum BTC balance
404
+ /// @return maxBtcBalance Value of the maximum BTC balance
405
+ function getWalletsParameters()
406
+ external
407
+ view
408
+ returns (
409
+ uint32 creationPeriod,
410
+ uint64 minBtcBalance,
411
+ uint64 maxBtcBalance
412
+ )
413
+ {
414
+ creationPeriod = wallets.creationPeriod;
415
+ minBtcBalance = wallets.minBtcBalance;
416
+ maxBtcBalance = wallets.maxBtcBalance;
417
+
418
+ return (creationPeriod, minBtcBalance, maxBtcBalance);
419
+ }
397
420
 
398
421
  /// @notice Allows the Governance to mark the given vault address as trusted
399
422
  /// or no longer trusted. Vaults are not trusted by default.
@@ -413,6 +436,93 @@ contract Bridge is Ownable {
413
436
  emit VaultStatusUpdated(vault, isTrusted);
414
437
  }
415
438
 
439
+ /// @notice Requests creation of a new wallet. This function just
440
+ /// forms a request and the creation process is performed
441
+ /// asynchronously. Once a wallet is created, the ECDSA Wallet
442
+ /// Registry will notify this contract by calling the
443
+ /// `__ecdsaWalletCreatedCallback` function.
444
+ /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as
445
+ /// currently known on the Ethereum chain.
446
+ /// @dev Requirements:
447
+ /// - `activeWalletMainUtxo` components must point to the recent main
448
+ /// UTXO of the given active wallet, as currently known on the
449
+ /// Ethereum chain. If there is no active wallet at the moment, or
450
+ /// the active wallet has no main UTXO, this parameter can be
451
+ /// empty as it is ignored.
452
+ /// - Wallet creation must not be in progress
453
+ /// - If the active wallet is set, one of the following
454
+ /// conditions must be true:
455
+ /// - The active wallet BTC balance is above the minimum threshold
456
+ /// and the active wallet is old enough, i.e. the creation period
457
+ /// was elapsed since its creation time
458
+ /// - The active wallet BTC balance is above the maximum threshold
459
+ function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)
460
+ external
461
+ {
462
+ wallets.requestNewWallet(activeWalletMainUtxo);
463
+ }
464
+
465
+ /// @notice A callback function that is called by the ECDSA Wallet Registry
466
+ /// once a new ECDSA wallet is created.
467
+ /// @param ecdsaWalletID Wallet's unique identifier.
468
+ /// @param publicKeyX Wallet's public key's X coordinate.
469
+ /// @param publicKeyY Wallet's public key's Y coordinate.
470
+ /// @dev Requirements:
471
+ /// - The only caller authorized to call this function is `registry`
472
+ /// - Given wallet data must not belong to an already registered wallet
473
+ function __ecdsaWalletCreatedCallback(
474
+ bytes32 ecdsaWalletID,
475
+ bytes32 publicKeyX,
476
+ bytes32 publicKeyY
477
+ ) external override {
478
+ wallets.registerNewWallet(ecdsaWalletID, publicKeyX, publicKeyY);
479
+ }
480
+
481
+ // TODO: Documentation.
482
+ function __ecdsaWalletHeartbeatFailedCallback(
483
+ bytes32 ecdsaWalletID,
484
+ bytes32 publicKeyX,
485
+ bytes32 publicKeyY
486
+ ) external override {
487
+ // TODO: Implementation.
488
+ }
489
+
490
+ /// @notice Gets details about a registered wallet.
491
+ /// @param walletPubKeyHash The 20-byte wallet public key hash (computed
492
+ /// using Bitcoin HASH160 over the compressed ECDSA public key)
493
+ /// @return Wallet details.
494
+ function getWallet(bytes20 walletPubKeyHash)
495
+ external
496
+ view
497
+ returns (Wallets.Wallet memory)
498
+ {
499
+ return wallets.registeredWallets[walletPubKeyHash];
500
+ }
501
+
502
+ /// @notice Gets the public key hash of the active wallet.
503
+ /// @return The 20-byte public key hash (computed using Bitcoin HASH160
504
+ /// over the compressed ECDSA public key) of the active wallet.
505
+ /// Returns bytes20(0) if there is no active wallet at the moment.
506
+ function getActiveWalletPubKeyHash() external view returns (bytes20) {
507
+ return wallets.activeWalletPubKeyHash;
508
+ }
509
+
510
+ /// @notice Determines the current Bitcoin SPV proof difficulty context.
511
+ /// @return proofDifficulty Bitcoin proof difficulty context.
512
+ function proofDifficultyContext()
513
+ internal
514
+ view
515
+ returns (BitcoinTx.ProofDifficulty memory proofDifficulty)
516
+ {
517
+ proofDifficulty.currentEpochDifficulty = relay
518
+ .getCurrentEpochDifficulty();
519
+ proofDifficulty.previousEpochDifficulty = relay
520
+ .getPrevEpochDifficulty();
521
+ proofDifficulty.difficultyFactor = txProofDifficultyFactor;
522
+
523
+ return proofDifficulty;
524
+ }
525
+
416
526
  /// @notice Used by the depositor to reveal information about their P2(W)SH
417
527
  /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
418
528
  /// wallet listens for revealed deposit events and may decide to
@@ -455,7 +565,7 @@ contract Bridge is Ownable {
455
565
  "Vault is not trusted"
456
566
  );
457
567
 
458
- // TODO: Validate if `walletPubKeyHash` is a known and active wallet.
568
+ // TODO: Validate if `walletPubKeyHash` is a known and live wallet.
459
569
  // TODO: Should we enforce a specific locktime at contract level?
460
570
 
461
571
  bytes memory expectedScript = abi.encodePacked(
@@ -536,7 +646,10 @@ contract Bridge is Ownable {
536
646
 
537
647
  uint64 fundingOutputAmount = fundingOutput.extractValue();
538
648
 
539
- // TODO: Check the amount against the dust threshold.
649
+ require(
650
+ fundingOutputAmount >= depositDustThreshold,
651
+ "Deposit amount too small"
652
+ );
540
653
 
541
654
  deposit.amount = fundingOutputAmount;
542
655
  deposit.depositor = reveal.depositor;
@@ -608,7 +721,11 @@ contract Bridge is Ownable {
608
721
  // can assume the transaction happened on Bitcoin chain and has
609
722
  // a sufficient number of confirmations as determined by
610
723
  // `txProofDifficultyFactor` constant.
611
- bytes32 sweepTxHash = validateBitcoinTxProof(sweepTx, sweepProof);
724
+ bytes32 sweepTxHash = BitcoinTx.validateProof(
725
+ sweepTx,
726
+ sweepProof,
727
+ proofDifficultyContext()
728
+ );
612
729
 
613
730
  // Process sweep transaction output and extract its target wallet
614
731
  // public key hash and value.
@@ -617,7 +734,11 @@ contract Bridge is Ownable {
617
734
  uint64 sweepTxOutputValue
618
735
  ) = processSweepTxOutput(sweepTx.outputVector);
619
736
 
620
- // TODO: Validate if `walletPubKeyHash` is a known and active wallet.
737
+ Wallets.Wallet storage wallet = wallets.registeredWallets[
738
+ walletPubKeyHash
739
+ ];
740
+
741
+ // TODO: Validate if `walletPubKeyHash` is a known and live wallet.
621
742
 
622
743
  // Check if the main UTXO for given wallet exists. If so, validate
623
744
  // passed main UTXO data against the stored hash and use them for
@@ -627,7 +748,7 @@ contract Bridge is Ownable {
627
748
  0,
628
749
  0
629
750
  );
630
- bytes32 mainUtxoHash = mainUtxos[walletPubKeyHash];
751
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
631
752
  if (mainUtxoHash != bytes32(0)) {
632
753
  require(
633
754
  keccak256(
@@ -695,7 +816,7 @@ contract Bridge is Ownable {
695
816
  // Record this sweep data and assign them to the wallet public key hash
696
817
  // as new main UTXO. Transaction output index is always 0 as sweep
697
818
  // transaction always contains only one output.
698
- mainUtxos[walletPubKeyHash] = keccak256(
819
+ wallet.mainUtxoHash = keccak256(
699
820
  abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)
700
821
  );
701
822
 
@@ -712,103 +833,6 @@ contract Bridge is Ownable {
712
833
  // TODO: Handle deposits having `vault` set.
713
834
  }
714
835
 
715
- /// @notice Validates the SPV proof of the Bitcoin transaction.
716
- /// Reverts in case the validation or proof verification fail.
717
- /// @param txInfo Bitcoin transaction data
718
- /// @param proof Bitcoin proof data
719
- /// @return txHash Proven 32-byte transaction hash.
720
- function validateBitcoinTxProof(
721
- BitcoinTx.Info calldata txInfo,
722
- BitcoinTx.Proof calldata proof
723
- ) internal view returns (bytes32 txHash) {
724
- require(
725
- txInfo.inputVector.validateVin(),
726
- "Invalid input vector provided"
727
- );
728
- require(
729
- txInfo.outputVector.validateVout(),
730
- "Invalid output vector provided"
731
- );
732
-
733
- txHash = abi
734
- .encodePacked(
735
- txInfo.version,
736
- txInfo.inputVector,
737
- txInfo.outputVector,
738
- txInfo.locktime
739
- )
740
- .hash256View();
741
-
742
- checkProofFromTxHash(txHash, proof);
743
-
744
- return txHash;
745
- }
746
-
747
- /// @notice Checks the given Bitcoin transaction hash against the SPV proof.
748
- /// Reverts in case the check fails.
749
- /// @param txHash 32-byte hash of the checked Bitcoin transaction
750
- /// @param proof Bitcoin proof data
751
- function checkProofFromTxHash(
752
- bytes32 txHash,
753
- BitcoinTx.Proof calldata proof
754
- ) internal view {
755
- require(
756
- txHash.prove(
757
- proof.bitcoinHeaders.extractMerkleRootLE(),
758
- proof.merkleProof,
759
- proof.txIndexInBlock
760
- ),
761
- "Tx merkle proof is not valid for provided header and tx hash"
762
- );
763
-
764
- evaluateProofDifficulty(proof.bitcoinHeaders);
765
- }
766
-
767
- /// @notice Evaluates the given Bitcoin proof difficulty against the actual
768
- /// Bitcoin chain difficulty provided by the relay oracle.
769
- /// Reverts in case the evaluation fails.
770
- /// @param bitcoinHeaders Bitcoin headers chain being part of the SPV
771
- /// proof. Used to extract the observed proof difficulty
772
- function evaluateProofDifficulty(bytes memory bitcoinHeaders)
773
- internal
774
- view
775
- {
776
- uint256 requestedDiff = 0;
777
- uint256 currentDiff = relay.getCurrentEpochDifficulty();
778
- uint256 previousDiff = relay.getPrevEpochDifficulty();
779
- uint256 firstHeaderDiff = bitcoinHeaders
780
- .extractTarget()
781
- .calculateDifficulty();
782
-
783
- if (firstHeaderDiff == currentDiff) {
784
- requestedDiff = currentDiff;
785
- } else if (firstHeaderDiff == previousDiff) {
786
- requestedDiff = previousDiff;
787
- } else {
788
- revert("Not at current or previous difficulty");
789
- }
790
-
791
- uint256 observedDiff = bitcoinHeaders.validateHeaderChain();
792
-
793
- require(
794
- observedDiff != ValidateSPV.getErrBadLength(),
795
- "Invalid length of the headers chain"
796
- );
797
- require(
798
- observedDiff != ValidateSPV.getErrInvalidChain(),
799
- "Invalid headers chain"
800
- );
801
- require(
802
- observedDiff != ValidateSPV.getErrLowWork(),
803
- "Insufficient work in a header"
804
- );
805
-
806
- require(
807
- observedDiff >= requestedDiff * txProofDifficultyFactor,
808
- "Insufficient accumulated difficulty in header chain"
809
- );
810
- }
811
-
812
836
  /// @notice Processes the Bitcoin sweep transaction output vector by
813
837
  /// extracting the single output and using it to gain additional
814
838
  /// information required for further processing (e.g. value and
@@ -1063,7 +1087,7 @@ contract Bridge is Ownable {
1063
1087
  /// @notice Requests redemption of the given amount from the specified
1064
1088
  /// wallet to the redeemer Bitcoin output script.
1065
1089
  /// @param walletPubKeyHash The 20-byte wallet public key hash (computed
1066
- // using Bitcoin HASH160 over the compressed ECDSA public key)
1090
+ /// using Bitcoin HASH160 over the compressed ECDSA public key)
1067
1091
  /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
1068
1092
  /// the Ethereum chain
1069
1093
  /// @param redeemerOutputScript The redeemer's length-prefixed output
@@ -1078,7 +1102,7 @@ contract Bridge is Ownable {
1078
1102
  /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.
1079
1103
  /// Fees values are taken at the moment of request creation.
1080
1104
  /// @dev Requirements:
1081
- /// - Wallet behind `walletPubKeyHash` must be active
1105
+ /// - Wallet behind `walletPubKeyHash` must be live
1082
1106
  /// - `mainUtxo` components must point to the recent main UTXO
1083
1107
  /// of the given wallet, as currently known on the Ethereum chain.
1084
1108
  /// - `redeemerOutputScript` must be a proper Bitcoin script
@@ -1095,12 +1119,16 @@ contract Bridge is Ownable {
1095
1119
  bytes calldata redeemerOutputScript,
1096
1120
  uint64 amount
1097
1121
  ) external {
1122
+ Wallets.Wallet storage wallet = wallets.registeredWallets[
1123
+ walletPubKeyHash
1124
+ ];
1125
+
1098
1126
  require(
1099
- wallets[walletPubKeyHash].state == WalletState.Active,
1100
- "Wallet must be in Active state"
1127
+ wallet.state == Wallets.WalletState.Live,
1128
+ "Wallet must be in Live state"
1101
1129
  );
1102
1130
 
1103
- bytes32 mainUtxoHash = mainUtxos[walletPubKeyHash];
1131
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
1104
1132
  require(
1105
1133
  mainUtxoHash != bytes32(0),
1106
1134
  "No main UTXO for the given wallet"
@@ -1162,7 +1190,7 @@ contract Bridge is Ownable {
1162
1190
 
1163
1191
  // Check if given redemption key is not used by a pending redemption.
1164
1192
  // There is no need to check for existence in `timedOutRedemptions`
1165
- // since the wallet's state is changed to other than Active after
1193
+ // since the wallet's state is changed to other than Live after
1166
1194
  // first time out is reported so making new requests is not possible.
1167
1195
  // slither-disable-next-line incorrect-equality
1168
1196
  require(
@@ -1183,12 +1211,9 @@ contract Bridge is Ownable {
1183
1211
  // wallet we need to subtract the total value of all pending redemptions
1184
1212
  // from that wallet's main UTXO value. Given that the treasury fee is
1185
1213
  // not redeemed from the wallet, we are subtracting it.
1186
- wallets[walletPubKeyHash].pendingRedemptionsValue +=
1187
- amount -
1188
- treasuryFee;
1214
+ wallet.pendingRedemptionsValue += amount - treasuryFee;
1189
1215
  require(
1190
- mainUtxo.txOutputValue >=
1191
- wallets[walletPubKeyHash].pendingRedemptionsValue,
1216
+ mainUtxo.txOutputValue >= wallet.pendingRedemptionsValue,
1192
1217
  "Insufficient wallet funds"
1193
1218
  );
1194
1219
 
@@ -1271,9 +1296,10 @@ contract Bridge is Ownable {
1271
1296
  // can assume the transaction happened on Bitcoin chain and has
1272
1297
  // a sufficient number of confirmations as determined by
1273
1298
  // `txProofDifficultyFactor` constant.
1274
- bytes32 redemptionTxHash = validateBitcoinTxProof(
1299
+ bytes32 redemptionTxHash = BitcoinTx.validateProof(
1275
1300
  redemptionTx,
1276
- redemptionProof
1301
+ redemptionProof,
1302
+ proofDifficultyContext()
1277
1303
  );
1278
1304
 
1279
1305
  // Perform validation of the redemption transaction input. Specifically,
@@ -1284,11 +1310,15 @@ contract Bridge is Ownable {
1284
1310
  walletPubKeyHash
1285
1311
  );
1286
1312
 
1287
- WalletState walletState = wallets[walletPubKeyHash].state;
1313
+ Wallets.Wallet storage wallet = wallets.registeredWallets[
1314
+ walletPubKeyHash
1315
+ ];
1316
+
1317
+ Wallets.WalletState walletState = wallet.state;
1288
1318
  require(
1289
- walletState == WalletState.Active ||
1290
- walletState == WalletState.MovingFunds,
1291
- "Wallet must be in Active or MovingFuds state"
1319
+ walletState == Wallets.WalletState.Live ||
1320
+ walletState == Wallets.WalletState.MovingFunds,
1321
+ "Wallet must be in Live or MovingFuds state"
1292
1322
  );
1293
1323
 
1294
1324
  // Process redemption transaction outputs to extract some info required
@@ -1301,7 +1331,7 @@ contract Bridge is Ownable {
1301
1331
  if (outputsInfo.changeValue > 0) {
1302
1332
  // If the change value is grater than zero, it means the change
1303
1333
  // output exists and can be used as new wallet's main UTXO.
1304
- mainUtxos[walletPubKeyHash] = keccak256(
1334
+ wallet.mainUtxoHash = keccak256(
1305
1335
  abi.encodePacked(
1306
1336
  redemptionTxHash,
1307
1337
  outputsInfo.changeIndex,
@@ -1312,11 +1342,10 @@ contract Bridge is Ownable {
1312
1342
  // If the change value is zero, it means the change output doesn't
1313
1343
  // exists and no funds left on the wallet. Delete the main UTXO
1314
1344
  // for that wallet to represent that state in a proper way.
1315
- delete mainUtxos[walletPubKeyHash];
1345
+ delete wallet.mainUtxoHash;
1316
1346
  }
1317
1347
 
1318
- wallets[walletPubKeyHash].pendingRedemptionsValue -= outputsInfo
1319
- .totalBurnableValue;
1348
+ wallet.pendingRedemptionsValue -= outputsInfo.totalBurnableValue;
1320
1349
 
1321
1350
  emit RedemptionsCompleted(walletPubKeyHash, redemptionTxHash);
1322
1351
 
@@ -1342,7 +1371,9 @@ contract Bridge is Ownable {
1342
1371
  bytes20 walletPubKeyHash
1343
1372
  ) internal view {
1344
1373
  // Assert that main UTXO for passed wallet exists in storage.
1345
- bytes32 mainUtxoHash = mainUtxos[walletPubKeyHash];
1374
+ bytes32 mainUtxoHash = wallets
1375
+ .registeredWallets[walletPubKeyHash]
1376
+ .mainUtxoHash;
1346
1377
  require(mainUtxoHash != bytes32(0), "No main UTXO for given wallet");
1347
1378
 
1348
1379
  // Assert that passed main UTXO parameter is the same as in storage and
@@ -1605,7 +1636,7 @@ contract Bridge is Ownable {
1605
1636
  // by copying the entire `RedemptionRequest` struct there. No need
1606
1637
  // to check if `timedOutRedemptions` mapping already contains
1607
1638
  // that key because `requestRedemption` blocks requests targeting
1608
- // non-active wallets. Because `notifyRedemptionTimeout` changes
1639
+ // non-live wallets. Because `notifyRedemptionTimeout` changes
1609
1640
  // wallet state after first call (point 9), there is no possibility
1610
1641
  // that the given redemption key could be reported as timed out
1611
1642
  // multiple times. At the same time, if the given redemption key
@@ -1658,7 +1689,7 @@ contract Bridge is Ownable {
1658
1689
  // to have a bad input vector.
1659
1690
  // 2. Perform SPV proof to make sure it occurred on Bitcoin chain.
1660
1691
  // If not - revert.
1661
- // 3. Check if wallet state is Active or MovingFunds. If not, revert.
1692
+ // 3. Check if wallet state is Live or MovingFunds. If not, revert.
1662
1693
  // 4. Validate the number of inputs. If there is one input and it
1663
1694
  // points to the wallet's main UTXO - move to point 5. If there
1664
1695
  // are multiple inputs and there is wallet's main UTXO in the set,
@@ -0,0 +1,30 @@
1
+ pragma solidity ^0.8.9;
2
+
3
+ import "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
4
+
5
+ library EcdsaLib {
6
+ using BytesLib for bytes;
7
+
8
+ /// @notice Converts public key X and Y coordinates (32-byte each) to a
9
+ /// compressed public key (33-byte). Compressed public key is X
10
+ /// coordinate prefixed with `02` or `03` based on the Y coordinate parity.
11
+ /// It is expected that the uncompressed public key is stripped
12
+ /// (i.e. it is not prefixed with `04`).
13
+ /// @param x Wallet's public key's X coordinate.
14
+ /// @param y Wallet's public key's Y coordinate.
15
+ /// @return Compressed public key (33-byte), prefixed with `02` or `03`.
16
+ function compressPublicKey(bytes32 x, bytes32 y)
17
+ internal
18
+ pure
19
+ returns (bytes memory)
20
+ {
21
+ bytes1 prefix;
22
+ if (uint256(y) % 2 == 0) {
23
+ prefix = hex"02";
24
+ } else {
25
+ prefix = hex"03";
26
+ }
27
+
28
+ return bytes.concat(prefix, x);
29
+ }
30
+ }