@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.
- package/README.adoc +12 -0
- package/artifacts/TBTC.json +11 -10
- package/artifacts/TBTCToken.json +11 -10
- package/artifacts/VendingMachine.json +12 -11
- package/artifacts/solcInputs/2676c70e1dffa939dbf0519ef3304b34.json +191 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +89 -3
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +337 -27
- package/build/contracts/bridge/Bridge.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
- package/build/contracts/bridge/Wallets.sol/Wallets.json +87 -0
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/BitcoinTx.sol +107 -2
- package/contracts/bridge/Bridge.sol +212 -181
- package/contracts/bridge/EcdsaLib.sol +30 -0
- package/contracts/bridge/Wallets.sol +352 -0
- package/package.json +21 -17
- 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 {
|
|
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
|
|
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 `
|
|
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
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1100
|
-
"Wallet must be in
|
|
1127
|
+
wallet.state == Wallets.WalletState.Live,
|
|
1128
|
+
"Wallet must be in Live state"
|
|
1101
1129
|
);
|
|
1102
1130
|
|
|
1103
|
-
bytes32 mainUtxoHash =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1313
|
+
Wallets.Wallet storage wallet = wallets.registeredWallets[
|
|
1314
|
+
walletPubKeyHash
|
|
1315
|
+
];
|
|
1316
|
+
|
|
1317
|
+
Wallets.WalletState walletState = wallet.state;
|
|
1288
1318
|
require(
|
|
1289
|
-
walletState == WalletState.
|
|
1290
|
-
walletState == WalletState.MovingFunds,
|
|
1291
|
-
"Wallet must be in
|
|
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
|
-
|
|
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
|
|
1345
|
+
delete wallet.mainUtxoHash;
|
|
1316
1346
|
}
|
|
1317
1347
|
|
|
1318
|
-
|
|
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 =
|
|
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-
|
|
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
|
|
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
|
+
}
|