@keep-network/tbtc-v2 0.1.1-dev.4 → 0.1.1-dev.42
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 +19 -18
- package/artifacts/TBTCToken.json +19 -18
- package/artifacts/VendingMachine.json +20 -19
- package/artifacts/solcInputs/002940e9cc8128f6629e90620c66cba5.json +215 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.json +20 -2
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +1664 -63
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
- package/build/contracts/bridge/BridgeState.sol/BridgeState.json +42 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
- 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/Fraud.sol/Fraud.dbg.json +4 -0
- package/build/contracts/bridge/Fraud.sol/Fraud.json +138 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +30 -0
- package/build/contracts/bridge/Redeem.sol/OutboundTx.dbg.json +4 -0
- package/build/contracts/bridge/Redeem.sol/OutboundTx.json +10 -0
- package/build/contracts/bridge/Redeem.sol/Redeem.dbg.json +4 -0
- package/build/contracts/bridge/Redeem.sol/Redeem.json +92 -0
- package/build/contracts/bridge/Sweep.sol/Sweep.dbg.json +4 -0
- package/build/contracts/bridge/Sweep.sol/Sweep.json +30 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
- package/build/contracts/bridge/Wallets.sol/Wallets.json +93 -0
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.json +2 -2
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.json +19 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +36 -18
- package/contracts/GovernanceUtils.sol +1 -1
- package/contracts/bank/Bank.sol +34 -18
- package/contracts/bridge/BitcoinTx.sol +241 -0
- package/contracts/bridge/Bridge.sol +942 -123
- package/contracts/bridge/BridgeState.sol +251 -0
- package/contracts/bridge/Deposit.sol +244 -0
- package/contracts/bridge/EcdsaLib.sol +30 -0
- package/contracts/bridge/Fraud.sol +455 -0
- package/contracts/bridge/IRelay.sol +28 -0
- package/contracts/bridge/MovingFunds.sol +278 -0
- package/contracts/bridge/Redeem.sol +844 -0
- package/contracts/bridge/Sweep.sol +509 -0
- package/contracts/bridge/VendingMachine.sol +1 -1
- package/contracts/bridge/Wallets.sol +510 -0
- package/contracts/token/TBTC.sol +1 -1
- package/contracts/vault/IVault.sol +32 -10
- package/contracts/vault/TBTCVault.sol +20 -2
- package/package.json +28 -24
- package/artifacts/solcInputs/d71966212a658480bad5748ad85b1396.json +0 -116
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity ^0.8.9;
|
|
17
|
+
|
|
18
|
+
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
|
|
19
|
+
import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
|
|
20
|
+
|
|
21
|
+
import "./BitcoinTx.sol";
|
|
22
|
+
import "./BridgeState.sol";
|
|
23
|
+
import "./Redeem.sol";
|
|
24
|
+
|
|
25
|
+
library MovingFunds {
|
|
26
|
+
using BridgeState for BridgeState.Storage;
|
|
27
|
+
using Wallets for BridgeState.Storage;
|
|
28
|
+
|
|
29
|
+
using BTCUtils for bytes;
|
|
30
|
+
using BytesLib for bytes;
|
|
31
|
+
|
|
32
|
+
event MovingFundsCompleted(
|
|
33
|
+
bytes20 walletPubKeyHash,
|
|
34
|
+
bytes32 movingFundsTxHash
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/// @notice Used by the wallet to prove the BTC moving funds transaction
|
|
38
|
+
/// and to make the necessary state changes. Moving funds is only
|
|
39
|
+
/// accepted if it satisfies SPV proof.
|
|
40
|
+
///
|
|
41
|
+
/// The function validates the moving funds transaction structure
|
|
42
|
+
/// by checking if it actually spends the main UTXO of the declared
|
|
43
|
+
/// wallet and locks the value on the pre-committed target wallets
|
|
44
|
+
/// using a reasonable transaction fee. If all preconditions are
|
|
45
|
+
/// met, this functions closes the source wallet.
|
|
46
|
+
///
|
|
47
|
+
/// It is possible to prove the given moving funds transaction only
|
|
48
|
+
/// one time.
|
|
49
|
+
/// @param movingFundsTx Bitcoin moving funds transaction data
|
|
50
|
+
/// @param movingFundsProof Bitcoin moving funds proof data
|
|
51
|
+
/// @param mainUtxo Data of the wallet's main UTXO, as currently known on
|
|
52
|
+
/// the Ethereum chain
|
|
53
|
+
/// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin
|
|
54
|
+
/// HASH160 over the compressed ECDSA public key) of the wallet
|
|
55
|
+
/// which performed the moving funds transaction
|
|
56
|
+
/// @dev Requirements:
|
|
57
|
+
/// - `movingFundsTx` components must match the expected structure. See
|
|
58
|
+
/// `BitcoinTx.Info` docs for reference. Their values must exactly
|
|
59
|
+
/// correspond to appropriate Bitcoin transaction fields to produce
|
|
60
|
+
/// a provable transaction hash.
|
|
61
|
+
/// - The `movingFundsTx` should represent a Bitcoin transaction with
|
|
62
|
+
/// exactly 1 input that refers to the wallet's main UTXO. That
|
|
63
|
+
/// transaction should have 1..n outputs corresponding to the
|
|
64
|
+
/// pre-committed target wallets. Outputs must be ordered in the
|
|
65
|
+
/// same way as their corresponding target wallets are ordered
|
|
66
|
+
/// within the target wallets commitment.
|
|
67
|
+
/// - `movingFundsProof` components must match the expected structure.
|
|
68
|
+
/// See `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`
|
|
69
|
+
/// field must contain a valid number of block headers, not less
|
|
70
|
+
/// than the `txProofDifficultyFactor` contract constant.
|
|
71
|
+
/// - `mainUtxo` components must point to the recent main UTXO
|
|
72
|
+
/// of the given wallet, as currently known on the Ethereum chain.
|
|
73
|
+
/// Additionally, the recent main UTXO on Ethereum must be set.
|
|
74
|
+
/// - `walletPubKeyHash` must be connected with the main UTXO used
|
|
75
|
+
/// as transaction single input.
|
|
76
|
+
/// - The wallet that `walletPubKeyHash` points to must be in the
|
|
77
|
+
/// MovingFunds state.
|
|
78
|
+
/// - The target wallets commitment must be submitted by the wallet
|
|
79
|
+
/// that `walletPubKeyHash` points to.
|
|
80
|
+
/// - The total Bitcoin transaction fee must be lesser or equal
|
|
81
|
+
/// to `movingFundsTxMaxTotalFee` governable parameter.
|
|
82
|
+
function submitMovingFundsProof(
|
|
83
|
+
BridgeState.Storage storage self,
|
|
84
|
+
BitcoinTx.Info calldata movingFundsTx,
|
|
85
|
+
BitcoinTx.Proof calldata movingFundsProof,
|
|
86
|
+
BitcoinTx.UTXO calldata mainUtxo,
|
|
87
|
+
bytes20 walletPubKeyHash
|
|
88
|
+
) external {
|
|
89
|
+
// The actual transaction proof is performed here. After that point, we
|
|
90
|
+
// can assume the transaction happened on Bitcoin chain and has
|
|
91
|
+
// a sufficient number of confirmations as determined by
|
|
92
|
+
// `txProofDifficultyFactor` constant.
|
|
93
|
+
bytes32 movingFundsTxHash = BitcoinTx.validateProof(
|
|
94
|
+
movingFundsTx,
|
|
95
|
+
movingFundsProof,
|
|
96
|
+
self.proofDifficultyContext()
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Process the moving funds transaction input. Specifically, check if
|
|
100
|
+
// it refers to the expected wallet's main UTXO.
|
|
101
|
+
OutboundTx.processWalletOutboundTxInput(
|
|
102
|
+
self,
|
|
103
|
+
movingFundsTx.inputVector,
|
|
104
|
+
mainUtxo,
|
|
105
|
+
walletPubKeyHash
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
(
|
|
109
|
+
bytes32 targetWalletsHash,
|
|
110
|
+
uint256 outputsTotalValue
|
|
111
|
+
) = processMovingFundsTxOutputs(movingFundsTx.outputVector);
|
|
112
|
+
|
|
113
|
+
require(
|
|
114
|
+
mainUtxo.txOutputValue - outputsTotalValue <=
|
|
115
|
+
self.movingFundsTxMaxTotalFee,
|
|
116
|
+
"Transaction fee is too high"
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
self.notifyWalletFundsMoved(walletPubKeyHash, targetWalletsHash);
|
|
120
|
+
|
|
121
|
+
emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// @notice Processes the moving funds Bitcoin transaction output vector
|
|
125
|
+
/// and extracts information required for further processing.
|
|
126
|
+
/// @param movingFundsTxOutputVector Bitcoin moving funds transaction output
|
|
127
|
+
/// vector. This function assumes vector's structure is valid so it
|
|
128
|
+
/// must be validated using e.g. `BTCUtils.validateVout` function
|
|
129
|
+
/// before it is passed here
|
|
130
|
+
/// @return targetWalletsHash keccak256 hash over the list of actual
|
|
131
|
+
/// target wallets used in the transaction.
|
|
132
|
+
/// @return outputsTotalValue Sum of all outputs values.
|
|
133
|
+
/// @dev Requirements:
|
|
134
|
+
/// - The `movingFundsTxOutputVector` must be parseable, i.e. must
|
|
135
|
+
/// be validated by the caller as stated in their parameter doc.
|
|
136
|
+
/// - Each output must refer to a 20-byte public key hash.
|
|
137
|
+
/// - The total outputs value must be evenly divided over all outputs.
|
|
138
|
+
function processMovingFundsTxOutputs(bytes memory movingFundsTxOutputVector)
|
|
139
|
+
internal
|
|
140
|
+
view
|
|
141
|
+
returns (bytes32 targetWalletsHash, uint256 outputsTotalValue)
|
|
142
|
+
{
|
|
143
|
+
// Determining the total number of Bitcoin transaction outputs in
|
|
144
|
+
// the same way as for number of inputs. See `BitcoinTx.outputVector`
|
|
145
|
+
// docs for more details.
|
|
146
|
+
(
|
|
147
|
+
uint256 outputsCompactSizeUintLength,
|
|
148
|
+
uint256 outputsCount
|
|
149
|
+
) = movingFundsTxOutputVector.parseVarInt();
|
|
150
|
+
|
|
151
|
+
// To determine the first output starting index, we must jump over
|
|
152
|
+
// the compactSize uint which prepends the output vector. One byte
|
|
153
|
+
// must be added because `BtcUtils.parseVarInt` does not include
|
|
154
|
+
// compactSize uint tag in the returned length.
|
|
155
|
+
//
|
|
156
|
+
// For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`
|
|
157
|
+
// returns `0`, so we jump over one byte of compactSize uint.
|
|
158
|
+
//
|
|
159
|
+
// For >= 253 && <= 0xffff there is `0xfd` tag,
|
|
160
|
+
// `BTCUtils.determineVarIntDataLengthAt` returns `2` (no
|
|
161
|
+
// tag byte included) so we need to jump over 1+2 bytes of
|
|
162
|
+
// compactSize uint.
|
|
163
|
+
//
|
|
164
|
+
// Please refer `BTCUtils` library and compactSize uint
|
|
165
|
+
// docs in `BitcoinTx` library for more details.
|
|
166
|
+
uint256 outputStartingIndex = 1 + outputsCompactSizeUintLength;
|
|
167
|
+
|
|
168
|
+
bytes20[] memory targetWallets = new bytes20[](outputsCount);
|
|
169
|
+
uint64[] memory outputsValues = new uint64[](outputsCount);
|
|
170
|
+
|
|
171
|
+
// Outputs processing loop.
|
|
172
|
+
for (uint256 i = 0; i < outputsCount; i++) {
|
|
173
|
+
uint256 outputLength = movingFundsTxOutputVector
|
|
174
|
+
.determineOutputLengthAt(outputStartingIndex);
|
|
175
|
+
|
|
176
|
+
bytes memory output = movingFundsTxOutputVector.slice(
|
|
177
|
+
outputStartingIndex,
|
|
178
|
+
outputLength
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Extract the output script payload.
|
|
182
|
+
bytes memory targetWalletPubKeyHashBytes = output.extractHash();
|
|
183
|
+
// Output script payload must refer to a known wallet public key
|
|
184
|
+
// hash which is always 20-byte.
|
|
185
|
+
require(
|
|
186
|
+
targetWalletPubKeyHashBytes.length == 20,
|
|
187
|
+
"Target wallet public key hash must have 20 bytes"
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
bytes20 targetWalletPubKeyHash = targetWalletPubKeyHashBytes
|
|
191
|
+
.slice20(0);
|
|
192
|
+
|
|
193
|
+
// The next step is making sure that the 20-byte public key hash
|
|
194
|
+
// is actually used in the right context of a P2PKH or P2WPKH
|
|
195
|
+
// output. To do so, we must extract the full script from the output
|
|
196
|
+
// and compare with the expected P2PKH and P2WPKH scripts
|
|
197
|
+
// referring to that 20-byte public key hash. The output consists
|
|
198
|
+
// of an 8-byte value and a variable length script. To extract the
|
|
199
|
+
// script we slice the output starting from 9th byte until the end.
|
|
200
|
+
bytes32 outputScriptKeccak = keccak256(
|
|
201
|
+
output.slice(8, output.length - 8)
|
|
202
|
+
);
|
|
203
|
+
// Build the expected P2PKH script which has the following byte
|
|
204
|
+
// format: <0x1976a914> <20-byte PKH> <0x88ac>. According to
|
|
205
|
+
// https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
|
|
206
|
+
// - 0x19: Byte length of the entire script
|
|
207
|
+
// - 0x76: OP_DUP
|
|
208
|
+
// - 0xa9: OP_HASH160
|
|
209
|
+
// - 0x14: Byte length of the public key hash
|
|
210
|
+
// - 0x88: OP_EQUALVERIFY
|
|
211
|
+
// - 0xac: OP_CHECKSIG
|
|
212
|
+
// which matches the P2PKH structure as per:
|
|
213
|
+
// https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash
|
|
214
|
+
bytes32 targetWalletP2PKHScriptKeccak = keccak256(
|
|
215
|
+
abi.encodePacked(
|
|
216
|
+
hex"1976a914",
|
|
217
|
+
targetWalletPubKeyHash,
|
|
218
|
+
hex"88ac"
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
// Build the expected P2WPKH script which has the following format:
|
|
222
|
+
// <0x160014> <20-byte PKH>. According to
|
|
223
|
+
// https://en.bitcoin.it/wiki/Script#Opcodes this translates to:
|
|
224
|
+
// - 0x16: Byte length of the entire script
|
|
225
|
+
// - 0x00: OP_0
|
|
226
|
+
// - 0x14: Byte length of the public key hash
|
|
227
|
+
// which matches the P2WPKH structure as per:
|
|
228
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH
|
|
229
|
+
bytes32 targetWalletP2WPKHScriptKeccak = keccak256(
|
|
230
|
+
abi.encodePacked(hex"160014", targetWalletPubKeyHash)
|
|
231
|
+
);
|
|
232
|
+
// Make sure the actual output script matches either the P2PKH
|
|
233
|
+
// or P2WPKH format.
|
|
234
|
+
require(
|
|
235
|
+
outputScriptKeccak == targetWalletP2PKHScriptKeccak ||
|
|
236
|
+
outputScriptKeccak == targetWalletP2WPKHScriptKeccak,
|
|
237
|
+
"Output must be P2PKH or P2WPKH"
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Add the wallet public key hash to the list that will be used
|
|
241
|
+
// to build the result list hash. There is no need to check if
|
|
242
|
+
// given output is a change here because the actual target wallet
|
|
243
|
+
// list must be exactly the same as the pre-committed target wallet
|
|
244
|
+
// list which is guaranteed to be valid.
|
|
245
|
+
targetWallets[i] = targetWalletPubKeyHash;
|
|
246
|
+
|
|
247
|
+
// Extract the value from given output.
|
|
248
|
+
outputsValues[i] = output.extractValue();
|
|
249
|
+
outputsTotalValue += outputsValues[i];
|
|
250
|
+
|
|
251
|
+
// Make the `outputStartingIndex` pointing to the next output by
|
|
252
|
+
// increasing it by current output's length.
|
|
253
|
+
outputStartingIndex += outputLength;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Compute the indivisible remainder that remains after dividing the
|
|
257
|
+
// outputs total value over all outputs evenly.
|
|
258
|
+
uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;
|
|
259
|
+
// Compute the minimum allowed output value by dividing the outputs
|
|
260
|
+
// total value (reduced by the remainder) by the number of outputs.
|
|
261
|
+
uint256 minOutputValue = (outputsTotalValue -
|
|
262
|
+
outputsTotalValueRemainder) / outputsCount;
|
|
263
|
+
// Maximum possible value is the minimum value with the remainder included.
|
|
264
|
+
uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;
|
|
265
|
+
|
|
266
|
+
for (uint256 i = 0; i < outputsCount; i++) {
|
|
267
|
+
require(
|
|
268
|
+
minOutputValue <= outputsValues[i] &&
|
|
269
|
+
outputsValues[i] <= maxOutputValue,
|
|
270
|
+
"Transaction amount is not distributed evenly"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
targetWalletsHash = keccak256(abi.encodePacked(targetWallets));
|
|
275
|
+
|
|
276
|
+
return (targetWalletsHash, outputsTotalValue);
|
|
277
|
+
}
|
|
278
|
+
}
|