@keep-network/tbtc-v2 0.1.1-dev.95 → 0.1.1-dev.98

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 (74) hide show
  1. package/artifacts/Bank.json +8 -8
  2. package/artifacts/Bridge.json +57 -57
  3. package/artifacts/Deposit.json +9 -9
  4. package/artifacts/DepositSweep.json +9 -9
  5. package/artifacts/EcdsaDkgValidator.json +1 -1
  6. package/artifacts/EcdsaInactivity.json +1 -1
  7. package/artifacts/EcdsaSortitionPool.json +2 -2
  8. package/artifacts/Fraud.json +9 -9
  9. package/artifacts/KeepRegistry.json +1 -1
  10. package/artifacts/KeepStake.json +2 -2
  11. package/artifacts/KeepToken.json +2 -2
  12. package/artifacts/KeepTokenStaking.json +1 -1
  13. package/artifacts/MovingFunds.json +9 -9
  14. package/artifacts/NuCypherStakingEscrow.json +1 -1
  15. package/artifacts/NuCypherToken.json +2 -2
  16. package/artifacts/RandomBeaconStub.json +1 -1
  17. package/artifacts/Redemption.json +9 -9
  18. package/artifacts/ReimbursementPool.json +2 -2
  19. package/artifacts/Relay.json +9 -9
  20. package/artifacts/T.json +2 -2
  21. package/artifacts/TBTC.json +10 -10
  22. package/artifacts/TBTCToken.json +10 -10
  23. package/artifacts/TBTCVault.json +15 -15
  24. package/artifacts/TokenStaking.json +1 -1
  25. package/artifacts/TokenholderGovernor.json +9 -9
  26. package/artifacts/TokenholderTimelock.json +8 -8
  27. package/artifacts/VendingMachine.json +11 -11
  28. package/artifacts/VendingMachineKeep.json +1 -1
  29. package/artifacts/VendingMachineNuCypher.json +1 -1
  30. package/artifacts/WalletRegistry.json +5 -5
  31. package/artifacts/WalletRegistryGovernance.json +2 -2
  32. package/artifacts/Wallets.json +9 -9
  33. package/artifacts/solcInputs/{b1eed895af5ae97fee0102eb17dac6e6.json → becdd5668a2170e95004d124119e4fcb.json} +10 -10
  34. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  35. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  36. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  37. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  38. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  39. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  40. package/build/contracts/bridge/Bridge.sol/Bridge.json +103 -103
  41. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  42. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
  43. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  44. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  45. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  46. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +2 -2
  47. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  48. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  49. package/build/contracts/bridge/Fraud.sol/Fraud.json +2 -2
  50. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  51. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  52. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  53. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +2 -2
  54. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  55. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +2 -2
  56. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  57. package/build/contracts/bridge/Redemption.sol/Redemption.json +2 -2
  58. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  59. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  60. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  61. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  62. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  63. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  64. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  65. package/contracts/bridge/BitcoinTx.sol +72 -39
  66. package/contracts/bridge/Bridge.sol +14 -14
  67. package/contracts/bridge/BridgeState.sol +83 -75
  68. package/contracts/bridge/DepositSweep.sol +3 -0
  69. package/contracts/bridge/Fraud.sol +7 -32
  70. package/contracts/bridge/MovingFunds.sol +41 -53
  71. package/contracts/bridge/Redemption.sol +143 -108
  72. package/contracts/bridge/Wallets.sol +309 -143
  73. package/export.json +44 -44
  74. package/package.json +2 -2
@@ -42,34 +42,11 @@ library OutboundTx {
42
42
  /// before it is passed here.
43
43
  /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
44
44
  /// the Ethereum chain.
45
- /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
46
- // HASH160 over the compressed ECDSA public key) of the wallet which
47
- /// performed the outbound transaction.
48
45
  function processWalletOutboundTxInput(
49
46
  BridgeState.Storage storage self,
50
47
  bytes memory walletOutboundTxInputVector,
51
- BitcoinTx.UTXO calldata mainUtxo,
52
- bytes20 walletPubKeyHash
48
+ BitcoinTx.UTXO calldata mainUtxo
53
49
  ) internal {
54
- // Assert that main UTXO for passed wallet exists in storage.
55
- bytes32 mainUtxoHash = self
56
- .registeredWallets[walletPubKeyHash]
57
- .mainUtxoHash;
58
- require(mainUtxoHash != bytes32(0), "No main UTXO for given wallet");
59
-
60
- // Assert that passed main UTXO parameter is the same as in storage and
61
- // can be used for further processing.
62
- require(
63
- keccak256(
64
- abi.encodePacked(
65
- mainUtxo.txHash,
66
- mainUtxo.txOutputIndex,
67
- mainUtxo.txOutputValue
68
- )
69
- ) == mainUtxoHash,
70
- "Invalid main UTXO data"
71
- );
72
-
73
50
  // Assert that the single outbound transaction input actually
74
51
  // refers to the wallet's main UTXO.
75
52
  (
@@ -447,16 +424,14 @@ library Redemption {
447
424
  );
448
425
 
449
426
  // Validate if redeemer output script is a correct standard type
450
- // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by building a stub
451
- // output with 0 as value and using `BTCUtils.extractHash` on it. Such
452
- // a function extracts the payload properly only from standard outputs
453
- // so if it succeeds, we have a guarantee the redeemer output script
454
- // is proper. Worth to note `extractHash` ignores the value at all
455
- // so this is why we can use 0 safely. This way of validation is the
456
- // same as in tBTC v1.
457
- bytes memory redeemerOutputScriptPayload = abi
458
- .encodePacked(bytes8(0), redeemerOutputScript)
459
- .extractHash();
427
+ // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by using
428
+ // `BTCUtils.extractHashAt` on it. Such a function extracts the payload
429
+ // properly only from standard outputs so if it succeeds, we have a
430
+ // guarantee the redeemer output script is proper. The underlying way
431
+ // of validation is the same as in tBTC v1.
432
+ bytes memory redeemerOutputScriptPayload = redeemerOutputScript
433
+ .extractHashAt(0, redeemerOutputScript.length);
434
+
460
435
  require(
461
436
  redeemerOutputScriptPayload.length > 0,
462
437
  "Redeemer output script must be a standard type"
@@ -464,8 +439,8 @@ library Redemption {
464
439
  // Check if the redeemer output script payload does not point to the
465
440
  // wallet public key hash.
466
441
  require(
467
- keccak256(abi.encodePacked(walletPubKeyHash)) !=
468
- keccak256(redeemerOutputScriptPayload),
442
+ redeemerOutputScriptPayload.length != 20 ||
443
+ walletPubKeyHash != redeemerOutputScriptPayload.slice20(0),
469
444
  "Redeemer output script must not point to the wallet PKH"
470
445
  );
471
446
 
@@ -478,8 +453,9 @@ library Redemption {
478
453
  // and redeemer output script pair. That means there can be only one
479
454
  // request asking for redemption from the given wallet to the given
480
455
  // BTC script at the same time.
481
- uint256 redemptionKey = uint256(
482
- keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
456
+ uint256 redemptionKey = getRedemptionKey(
457
+ walletPubKeyHash,
458
+ redeemerOutputScript
483
459
  );
484
460
 
485
461
  // Check if given redemption key is not used by a pending redemption.
@@ -520,6 +496,7 @@ library Redemption {
520
496
  uint32(block.timestamp)
521
497
  );
522
498
 
499
+ // slither-disable-next-line reentrancy-events
523
500
  emit RedemptionRequested(
524
501
  walletPubKeyHash,
525
502
  redeemerOutputScript,
@@ -583,6 +560,9 @@ library Redemption {
583
560
  BitcoinTx.UTXO calldata mainUtxo,
584
561
  bytes20 walletPubKeyHash
585
562
  ) external {
563
+ // Wallet state validation is performed in the `resolveRedeemingWallet`
564
+ // function.
565
+
586
566
  // The actual transaction proof is performed here. After that point, we
587
567
  // can assume the transaction happened on Bitcoin chain and has
588
568
  // a sufficient number of confirmations as determined by
@@ -592,24 +572,18 @@ library Redemption {
592
572
  redemptionProof
593
573
  );
594
574
 
575
+ Wallets.Wallet storage wallet = resolveRedeemingWallet(
576
+ self,
577
+ walletPubKeyHash,
578
+ mainUtxo
579
+ );
580
+
595
581
  // Process the redemption transaction input. Specifically, check if it
596
582
  // refers to the expected wallet's main UTXO.
597
583
  OutboundTx.processWalletOutboundTxInput(
598
584
  self,
599
585
  redemptionTx.inputVector,
600
- mainUtxo,
601
- walletPubKeyHash
602
- );
603
-
604
- Wallets.Wallet storage wallet = self.registeredWallets[
605
- walletPubKeyHash
606
- ];
607
-
608
- Wallets.WalletState walletState = wallet.state;
609
- require(
610
- walletState == Wallets.WalletState.Live ||
611
- walletState == Wallets.WalletState.MovingFunds,
612
- "Wallet must be in Live or MovingFunds state"
586
+ mainUtxo
613
587
  );
614
588
 
615
589
  // Process redemption transaction outputs to extract some info required
@@ -645,6 +619,50 @@ library Redemption {
645
619
  self.bank.transferBalance(self.treasury, outputsInfo.totalTreasuryFee);
646
620
  }
647
621
 
622
+ /// @notice Resolves redeeming wallet based on the provided wallet public
623
+ /// key hash. Validates the wallet state and current main UTXO, as
624
+ /// currently known on the Ethereum chain.
625
+ /// @param walletPubKeyHash public key hash of the wallet proving the sweep
626
+ /// Bitcoin transaction.
627
+ /// @param mainUtxo Data of the wallet's main UTXO, as currently known on
628
+ /// the Ethereum chain.
629
+ /// @return wallet Data of the sweeping wallet.
630
+ /// @dev Requirements:
631
+ /// - Sweeping wallet must be either in Live or MovingFunds state,
632
+ /// - Main UTXO of the redeeming wallet must exists in the storage,
633
+ /// - The passed `mainUTXO` parameter must be equal to the stored one.
634
+ function resolveRedeemingWallet(
635
+ BridgeState.Storage storage self,
636
+ bytes20 walletPubKeyHash,
637
+ BitcoinTx.UTXO calldata mainUtxo
638
+ ) internal view returns (Wallets.Wallet storage wallet) {
639
+ wallet = self.registeredWallets[walletPubKeyHash];
640
+
641
+ // Assert that main UTXO for passed wallet exists in storage.
642
+ bytes32 mainUtxoHash = wallet.mainUtxoHash;
643
+ require(mainUtxoHash != bytes32(0), "No main UTXO for given wallet");
644
+
645
+ // Assert that passed main UTXO parameter is the same as in storage and
646
+ // can be used for further processing.
647
+ require(
648
+ keccak256(
649
+ abi.encodePacked(
650
+ mainUtxo.txHash,
651
+ mainUtxo.txOutputIndex,
652
+ mainUtxo.txOutputValue
653
+ )
654
+ ) == mainUtxoHash,
655
+ "Invalid main UTXO data"
656
+ );
657
+
658
+ Wallets.WalletState walletState = wallet.state;
659
+ require(
660
+ walletState == Wallets.WalletState.Live ||
661
+ walletState == Wallets.WalletState.MovingFunds,
662
+ "Wallet must be in Live or MovingFunds state"
663
+ );
664
+ }
665
+
648
666
  /// @notice Processes the Bitcoin redemption transaction output vector.
649
667
  /// It extracts each output and tries to identify it as a pending
650
668
  /// redemption request, reported timed out request, or change.
@@ -656,7 +674,7 @@ library Redemption {
656
674
  /// must be validated using e.g. `BTCUtils.validateVout` function
657
675
  /// before it is passed here.
658
676
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
659
- // HASH160 over the compressed ECDSA public key) of the wallet which
677
+ /// HASH160 over the compressed ECDSA public key) of the wallet which
660
678
  /// performed the redemption transaction.
661
679
  /// @return info Outcomes of the processing.
662
680
  function processRedemptionTxOutputs(
@@ -704,7 +722,7 @@ library Redemption {
704
722
  // which matches the P2PKH structure as per:
705
723
  // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
706
724
  bytes32 walletP2PKHScriptKeccak = keccak256(
707
- abi.encodePacked(hex"1976a914", walletPubKeyHash, hex"88ac")
725
+ abi.encodePacked(BitcoinTx.makeP2PKHScript(walletPubKeyHash))
708
726
  );
709
727
  // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.
710
728
  // According to https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
@@ -714,7 +732,7 @@ library Redemption {
714
732
  // which matches the P2WPKH structure as per:
715
733
  // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
716
734
  bytes32 walletP2WPKHScriptKeccak = keccak256(
717
- abi.encodePacked(hex"160014", walletPubKeyHash)
735
+ abi.encodePacked(BitcoinTx.makeP2WPKHScript(walletPubKeyHash))
718
736
  );
719
737
 
720
738
  return
@@ -733,7 +751,7 @@ library Redemption {
733
751
 
734
752
  /// @notice Processes all outputs from the redemption transaction. Tries to
735
753
  /// identify output as a change output, pending redemption request
736
- // or reported redemption. Reverts if one of the outputs cannot be
754
+ /// or reported redemption. Reverts if one of the outputs cannot be
737
755
  /// recognized properly. Marks each request as processed by removing
738
756
  /// them from `pendingRedemptions` mapping.
739
757
  /// @param redemptionTxOutputVector Bitcoin redemption transaction output
@@ -741,7 +759,7 @@ library Redemption {
741
759
  /// must be validated using e.g. `BTCUtils.validateVout` function
742
760
  /// before it is passed here.
743
761
  /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
744
- // HASH160 over the compressed ECDSA public key) of the wallet which
762
+ /// HASH160 over the compressed ECDSA public key) of the wallet which
745
763
  /// performed the redemption transaction.
746
764
  /// @param processInfo RedemptionTxOutputsProcessingInfo identifying output
747
765
  /// starting index, the number of outputs and possible wallet change
@@ -765,24 +783,33 @@ library Redemption {
765
783
  // https://github.com/keep-network/tbtc-v2/issues/257
766
784
  uint256 outputLength = redemptionTxOutputVector
767
785
  .determineOutputLengthAt(processInfo.outputStartingIndex);
768
- bytes memory output = redemptionTxOutputVector.slice(
769
- processInfo.outputStartingIndex,
770
- outputLength
771
- );
772
786
 
773
787
  // Extract the value from given output.
774
- uint64 outputValue = output.extractValue();
788
+ uint64 outputValue = redemptionTxOutputVector.extractValueAt(
789
+ processInfo.outputStartingIndex
790
+ );
791
+
792
+ uint256 scriptLength = outputLength - 8;
793
+
775
794
  // The output consists of an 8-byte value and a variable length
776
- // script. To extract that script we slice the output starting from
795
+ // script. To hash that script we slice the output starting from
777
796
  // 9th byte until the end.
778
- bytes memory outputScript = output.slice(8, output.length - 8);
797
+
798
+ uint256 outputScriptStart = processInfo.outputStartingIndex + 8;
799
+
800
+ bytes32 outputScriptHash;
801
+ /* solhint-disable-next-line no-inline-assembly */
802
+ assembly {
803
+ outputScriptHash := keccak256(
804
+ add(redemptionTxOutputVector, add(outputScriptStart, 32)),
805
+ scriptLength
806
+ )
807
+ }
779
808
 
780
809
  if (
781
810
  resultInfo.changeValue == 0 &&
782
- (keccak256(outputScript) ==
783
- processInfo.walletP2PKHScriptKeccak ||
784
- keccak256(outputScript) ==
785
- processInfo.walletP2WPKHScriptKeccak) &&
811
+ (outputScriptHash == processInfo.walletP2PKHScriptKeccak ||
812
+ outputScriptHash == processInfo.walletP2WPKHScriptKeccak) &&
786
813
  outputValue > 0
787
814
  ) {
788
815
  // If we entered here, that means the change output with a
@@ -797,8 +824,7 @@ library Redemption {
797
824
  uint64 treasuryFee
798
825
  ) = processNonChangeRedemptionTxOutput(
799
826
  self,
800
- walletPubKeyHash,
801
- outputScript,
827
+ _getRedemptionKey(walletPubKeyHash, outputScriptHash),
802
828
  outputValue
803
829
  );
804
830
  resultInfo.totalBurnableValue += burnableValue;
@@ -829,10 +855,7 @@ library Redemption {
829
855
  /// requested and reported timed-out redemption.
830
856
  /// This function also marks each pending request as processed by
831
857
  /// removing them from `pendingRedemptions` mapping.
832
- /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
833
- // HASH160 over the compressed ECDSA public key) of the wallet which
834
- /// performed the redemption transaction.
835
- /// @param outputScript Non-change output script to be processed.
858
+ /// @param redemptionKey Redemption key of the output being processed.
836
859
  /// @param outputValue Value of the output being processed.
837
860
  /// @return burnableValue The value burnable as a result of processing this
838
861
  /// single redemption output. This value needs to be summed up with
@@ -846,17 +869,9 @@ library Redemption {
846
869
  /// redemption request.
847
870
  function processNonChangeRedemptionTxOutput(
848
871
  BridgeState.Storage storage self,
849
- bytes20 walletPubKeyHash,
850
- bytes memory outputScript,
872
+ uint256 redemptionKey,
851
873
  uint64 outputValue
852
874
  ) internal returns (uint64 burnableValue, uint64 treasuryFee) {
853
- // This function should be called only if the given output is
854
- // supposed to represent a redemption. Build the redemption key
855
- // to perform that check.
856
- uint256 redemptionKey = uint256(
857
- keccak256(abi.encodePacked(walletPubKeyHash, outputScript))
858
- );
859
-
860
875
  if (self.pendingRedemptions[redemptionKey].requestedAt != 0) {
861
876
  // If we entered here, that means the output was identified
862
877
  // as a pending redemption request.
@@ -956,8 +971,10 @@ library Redemption {
956
971
  uint32[] calldata walletMembersIDs,
957
972
  bytes calldata redeemerOutputScript
958
973
  ) external {
959
- uint256 redemptionKey = uint256(
960
- keccak256(abi.encodePacked(walletPubKeyHash, redeemerOutputScript))
974
+ // Wallet state is validated in `notifyWalletRedemptionTimeout`.
975
+ uint256 redemptionKey = getRedemptionKey(
976
+ walletPubKeyHash,
977
+ redeemerOutputScript
961
978
  );
962
979
  Redemption.RedemptionRequest memory request = self.pendingRedemptions[
963
980
  redemptionKey
@@ -978,44 +995,62 @@ library Redemption {
978
995
  request.requestedAmount -
979
996
  request.treasuryFee;
980
997
 
981
- require(
982
- wallet.state == Wallets.WalletState.Live ||
983
- wallet.state == Wallets.WalletState.MovingFunds ||
984
- wallet.state == Wallets.WalletState.Terminated,
985
- "Wallet must be in Live, MovingFunds or Terminated state"
986
- );
987
-
988
998
  // It is worth noting that there is no need to check if
989
999
  // `timedOutRedemption` mapping already contains the given redemption
990
1000
  // key. There is no possibility to re-use a key of a reported timed-out
991
1001
  // redemption because the wallet responsible for causing the timeout is
992
1002
  // moved to a state that prevents it to receive new redemption requests.
993
1003
 
1004
+ // Propagate timeout consequences to the wallet
1005
+ self.notifyWalletRedemptionTimeout(walletPubKeyHash, walletMembersIDs);
1006
+
994
1007
  // Move the redemption from pending redemptions to timed-out redemptions
995
1008
  self.timedOutRedemptions[redemptionKey] = request;
996
1009
  delete self.pendingRedemptions[redemptionKey];
997
1010
 
998
- if (
999
- wallet.state == Wallets.WalletState.Live ||
1000
- wallet.state == Wallets.WalletState.MovingFunds
1001
- ) {
1002
- // Slash the wallet operators and reward the notifier
1003
- self.ecdsaWalletRegistry.seize(
1004
- self.redemptionTimeoutSlashingAmount,
1005
- self.redemptionTimeoutNotifierRewardMultiplier,
1006
- msg.sender,
1007
- wallet.ecdsaWalletID,
1008
- walletMembersIDs
1009
- );
1010
-
1011
- // Propagate timeout consequences to the wallet
1012
- self.notifyWalletTimedOutRedemption(walletPubKeyHash);
1013
- }
1014
-
1015
1011
  // slither-disable-next-line reentrancy-events
1016
1012
  emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);
1017
1013
 
1018
1014
  // Return the requested amount of tokens to the redeemer
1019
1015
  self.bank.transferBalance(request.redeemer, request.requestedAmount);
1020
1016
  }
1017
+
1018
+ /// @notice Calculate redemption key without allocations.
1019
+ /// @param walletPubKeyHash the pubkey hash of the wallet.
1020
+ /// @param script the output script of the redemption.
1021
+ /// @return The key = keccak256(keccak256(script), walletPubKeyHash).
1022
+ function getRedemptionKey(bytes20 walletPubKeyHash, bytes memory script)
1023
+ internal
1024
+ pure
1025
+ returns (uint256)
1026
+ {
1027
+ bytes32 scriptHash = keccak256(script);
1028
+ uint256 key;
1029
+ /* solhint-disable-next-line no-inline-assembly */
1030
+ assembly {
1031
+ mstore(0, scriptHash)
1032
+ mstore(32, walletPubKeyHash)
1033
+ key := keccak256(0, 52)
1034
+ }
1035
+ return key;
1036
+ }
1037
+
1038
+ /// @notice Finish calculating redemption key without allocations.
1039
+ /// @param walletPubKeyHash the pubkey hash of the wallet.
1040
+ /// @param scriptHash the output script hash of the redemption.
1041
+ /// @return The key = keccak256(scriptHash, walletPubKeyHash).
1042
+ function _getRedemptionKey(bytes20 walletPubKeyHash, bytes32 scriptHash)
1043
+ internal
1044
+ pure
1045
+ returns (uint256)
1046
+ {
1047
+ uint256 key;
1048
+ /* solhint-disable-next-line no-inline-assembly */
1049
+ assembly {
1050
+ mstore(0, scriptHash)
1051
+ mstore(32, walletPubKeyHash)
1052
+ key := keccak256(0, 52)
1053
+ }
1054
+ return key;
1055
+ }
1021
1056
  }