@keep-network/tbtc-v2 0.1.1-dev.5 → 0.1.1-dev.6
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/artifacts/TBTC.json +10 -10
- package/artifacts/TBTCToken.json +10 -10
- package/artifacts/VendingMachine.json +11 -11
- package/artifacts/solcInputs/{d71966212a658480bad5748ad85b1396.json → c4fd2c31cc58f5fe0cc586dd84a84b60.json} +10 -1
- 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/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.json +116 -71
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- 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/Bridge.sol +213 -71
- package/package.json +2 -1
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
pragma solidity 0.8.4;
|
|
17
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
|
+
|
|
18
21
|
/// @title BTC Bridge
|
|
19
22
|
/// @notice Bridge manages BTC deposit and redemption and is increasing and
|
|
20
23
|
/// decreasing balances in the Bank as a result of BTC deposit and
|
|
21
24
|
/// redemption operations.
|
|
22
25
|
///
|
|
23
26
|
/// Depositors send BTC funds to the most-recently-created-wallet of the
|
|
24
|
-
/// bridge using pay-to-script-hash (P2SH)
|
|
27
|
+
/// bridge using pay-to-script-hash (P2SH) or
|
|
28
|
+
/// pay-to-witness-script-hash (P2WSH) which contains hashed
|
|
25
29
|
/// information about the depositor’s minting Ethereum address. Then,
|
|
26
30
|
/// the depositor reveals their desired Ethereum minting address to the
|
|
27
31
|
/// Ethereum chain. The Bridge listens for these sorts of messages and
|
|
@@ -32,89 +36,234 @@ pragma solidity 0.8.4;
|
|
|
32
36
|
/// about the sweep increasing appropriate balances in the Bank.
|
|
33
37
|
/// @dev Bridge is an upgradeable component of the Bank.
|
|
34
38
|
contract Bridge {
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
using BTCUtils for bytes;
|
|
40
|
+
using BytesLib for bytes;
|
|
41
|
+
|
|
42
|
+
/// @notice Represents Bitcoin transaction data as described in:
|
|
43
|
+
/// https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
|
|
44
|
+
struct TxInfo {
|
|
45
|
+
// Transaction version number (4-byte LE).
|
|
46
|
+
bytes4 version;
|
|
47
|
+
// All transaction inputs prepended by the number of inputs encoded
|
|
48
|
+
// as a compactSize uint. Single vector item looks as follows:
|
|
49
|
+
// https://developer.bitcoin.org/reference/transactions.html#txin-a-transaction-input-non-coinbase
|
|
50
|
+
// though SegWit inputs don't contain the signature script (scriptSig).
|
|
51
|
+
// All encoded input transaction hashes are little-endian.
|
|
52
|
+
bytes inputVector;
|
|
53
|
+
// All transaction outputs prepended by the number of outputs encoded
|
|
54
|
+
// as a compactSize uint. Single vector item looks as follows:
|
|
55
|
+
// https://developer.bitcoin.org/reference/transactions.html#txout-a-transaction-output
|
|
56
|
+
bytes outputVector;
|
|
57
|
+
// Transaction locktime (4-byte LE).
|
|
58
|
+
bytes4 locktime;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// @notice Represents data which must be revealed by the depositor during
|
|
62
|
+
/// deposit reveal.
|
|
63
|
+
struct RevealInfo {
|
|
64
|
+
// Index of the funding output belonging to the funding transaction.
|
|
65
|
+
uint8 fundingOutputIndex;
|
|
66
|
+
// Ethereum depositor address.
|
|
67
|
+
address depositor;
|
|
68
|
+
// The blinding factor as 8 bytes. Byte endianness doesn't matter
|
|
69
|
+
// as this factor is not interpreted as uint.
|
|
70
|
+
bytes8 blindingFactor;
|
|
71
|
+
// The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
|
|
72
|
+
// of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
|
|
73
|
+
bytes20 walletPubKeyHash;
|
|
74
|
+
// The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
|
|
75
|
+
// that can be used to make the deposit refund after the refund
|
|
76
|
+
// locktime passes. Hashed in the HASH160 Bitcoin opcode style.
|
|
77
|
+
bytes20 refundPubKeyHash;
|
|
78
|
+
// The refund locktime (4-byte LE). Interpreted according to locktime
|
|
79
|
+
// parsing rules described in:
|
|
80
|
+
// https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
|
|
81
|
+
// and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
|
|
82
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
|
|
83
|
+
bytes4 refundLocktime;
|
|
84
|
+
// Address of the tBTC vault.
|
|
37
85
|
address vault;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// @notice Represents tBTC deposit data.
|
|
89
|
+
struct DepositInfo {
|
|
90
|
+
// Ethereum depositor address.
|
|
91
|
+
address depositor;
|
|
92
|
+
// Deposit amount in satoshi (8-byte LE). For example:
|
|
93
|
+
// 0.0001 BTC = 10000 satoshi = 0x1027000000000000
|
|
94
|
+
bytes8 amount;
|
|
95
|
+
// UNIX timestamp the deposit was revealed at.
|
|
38
96
|
uint32 revealedAt;
|
|
97
|
+
// Address of the tBTC vault.
|
|
98
|
+
address vault;
|
|
39
99
|
}
|
|
40
100
|
|
|
41
101
|
/// @notice Collection of all unswept deposits indexed by
|
|
42
|
-
/// keccak256(fundingTxHash | fundingOutputIndex
|
|
102
|
+
/// keccak256(fundingTxHash | fundingOutputIndex).
|
|
103
|
+
/// The fundingTxHash is LE bytes32 and fundingOutputIndex an uint8.
|
|
43
104
|
/// This mapping may contain valid and invalid deposits and the
|
|
44
105
|
/// wallet is responsible for validating them before attempting to
|
|
45
106
|
/// execute a sweep.
|
|
107
|
+
///
|
|
108
|
+
/// TODO: Explore the possibility of storing just a hash of DepositInfo.
|
|
46
109
|
mapping(uint256 => DepositInfo) public unswept;
|
|
47
110
|
|
|
48
111
|
event DepositRevealed(
|
|
49
|
-
uint256 depositId,
|
|
50
112
|
bytes32 fundingTxHash,
|
|
51
113
|
uint8 fundingOutputIndex,
|
|
52
114
|
address depositor,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
115
|
+
bytes8 blindingFactor,
|
|
116
|
+
bytes20 walletPubKeyHash,
|
|
117
|
+
bytes20 refundPubKeyHash,
|
|
118
|
+
bytes4 refundLocktime
|
|
57
119
|
);
|
|
58
120
|
|
|
59
|
-
/// @notice Used by the depositor to reveal information about their
|
|
121
|
+
/// @notice Used by the depositor to reveal information about their P2(W)SH
|
|
60
122
|
/// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
|
|
61
123
|
/// wallet listens for revealed deposit events and may decide to
|
|
62
124
|
/// include the revealed deposit in the next executed sweep.
|
|
63
125
|
/// Information about the Bitcoin deposit can be revealed before or
|
|
64
|
-
/// after the Bitcoin transaction with
|
|
65
|
-
/// Bitcoin chain.
|
|
66
|
-
///
|
|
67
|
-
///
|
|
68
|
-
/// @param
|
|
69
|
-
///
|
|
70
|
-
/// @param blindingFactor The blinding factor used in the BTC P2SH deposit,
|
|
71
|
-
/// max 2^64
|
|
72
|
-
/// @param refundPubKey The refund pub key used in the BTC P2SH deposit
|
|
73
|
-
/// @param amount The amount locked in the BTC P2SH deposit
|
|
74
|
-
/// @param vault Bank vault to which the swept deposit should be routed
|
|
126
|
+
/// after the Bitcoin transaction with P2(W)SH deposit is mined on
|
|
127
|
+
/// the Bitcoin chain. Worth noting the gas cost of this function
|
|
128
|
+
/// scales with the number of P2(W)SH transaction inputs and
|
|
129
|
+
/// outputs.
|
|
130
|
+
/// @param fundingTx Bitcoin funding transaction data.
|
|
131
|
+
/// @param reveal Deposit reveal data.
|
|
75
132
|
/// @dev Requirements:
|
|
76
|
-
/// - `
|
|
77
|
-
///
|
|
78
|
-
/// - `
|
|
79
|
-
///
|
|
133
|
+
/// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH
|
|
134
|
+
/// output of the BTC deposit transaction
|
|
135
|
+
/// - `reveal.depositor` must be the Ethereum address used in the
|
|
136
|
+
/// P2(W)SH BTC deposit transaction,
|
|
137
|
+
/// - `reveal.blindingFactor` must be the blinding factor used in the
|
|
138
|
+
/// P2(W)SH BTC deposit transaction,
|
|
139
|
+
/// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in
|
|
140
|
+
/// the P2(W)SH BTC deposit transaction,
|
|
141
|
+
/// - `reveal.refundPubKeyHash` must be the refund pub key hash used in
|
|
142
|
+
/// the P2(W)SH BTC deposit transaction,
|
|
143
|
+
/// - `reveal.refundLocktime` must be the refund locktime used in the
|
|
144
|
+
/// P2(W)SH BTC deposit transaction,
|
|
80
145
|
/// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`
|
|
81
|
-
/// can be revealed
|
|
146
|
+
/// can be revealed only one time.
|
|
82
147
|
///
|
|
83
148
|
/// If any of these requirements is not met, the wallet _must_ refuse
|
|
84
149
|
/// to sweep the deposit and the depositor has to wait until the
|
|
85
150
|
/// deposit script unlocks to receive their BTC back.
|
|
86
151
|
function revealDeposit(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
uint64 blindingFactor,
|
|
90
|
-
bytes calldata refundPubKey,
|
|
91
|
-
uint64 amount,
|
|
92
|
-
address vault
|
|
152
|
+
TxInfo calldata fundingTx,
|
|
153
|
+
RevealInfo calldata reveal
|
|
93
154
|
) external {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
bytes memory expectedScript =
|
|
156
|
+
abi.encodePacked(
|
|
157
|
+
hex"14", // Byte length of depositor Ethereum address.
|
|
158
|
+
reveal.depositor,
|
|
159
|
+
hex"75", // OP_DROP
|
|
160
|
+
hex"08", // Byte length of blinding factor value.
|
|
161
|
+
reveal.blindingFactor,
|
|
162
|
+
hex"75", // OP_DROP
|
|
163
|
+
hex"76", // OP_DUP
|
|
164
|
+
hex"a9", // OP_HASH160
|
|
165
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
166
|
+
reveal.walletPubKeyHash,
|
|
167
|
+
hex"87", // OP_EQUAL
|
|
168
|
+
hex"63", // OP_IF
|
|
169
|
+
hex"ac", // OP_CHECKSIG
|
|
170
|
+
hex"67", // OP_ELSE
|
|
171
|
+
hex"76", // OP_DUP
|
|
172
|
+
hex"a9", // OP_HASH160
|
|
173
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
174
|
+
reveal.refundPubKeyHash,
|
|
175
|
+
hex"88", // OP_EQUALVERIFY
|
|
176
|
+
hex"04", // Byte length of refund locktime value.
|
|
177
|
+
reveal.refundLocktime,
|
|
178
|
+
hex"b1", // OP_CHECKLOCKTIMEVERIFY
|
|
179
|
+
hex"75", // OP_DROP
|
|
180
|
+
hex"ac", // OP_CHECKSIG
|
|
181
|
+
hex"68" // OP_ENDIF
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
bytes memory fundingOutput =
|
|
185
|
+
fundingTx.outputVector.extractOutputAtIndex(
|
|
186
|
+
reveal.fundingOutputIndex
|
|
187
|
+
);
|
|
188
|
+
bytes memory fundingOutputHash = fundingOutput.extractHash();
|
|
189
|
+
|
|
190
|
+
if (fundingOutputHash.length == 20) {
|
|
191
|
+
// A 20-byte output hash is used by P2SH. That hash is constructed
|
|
192
|
+
// by applying OP_HASH160 on the locking script. A 20-byte output
|
|
193
|
+
// hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
|
|
194
|
+
// public key). However, since we compare the actual output hash
|
|
195
|
+
// with an expected locking script hash, this check will succeed only
|
|
196
|
+
// for P2SH transaction type with expected script hash value. For
|
|
197
|
+
// P2PKH and P2WPKH, it will fail on the output hash comparison with
|
|
198
|
+
// the expected locking script hash.
|
|
199
|
+
require(
|
|
200
|
+
keccak256(fundingOutputHash) ==
|
|
201
|
+
keccak256(expectedScript.hash160()),
|
|
202
|
+
"Wrong 20-byte script hash"
|
|
203
|
+
);
|
|
204
|
+
} else if (fundingOutputHash.length == 32) {
|
|
205
|
+
// A 32-byte output hash is used by P2WSH. That hash is constructed
|
|
206
|
+
// by applying OP_HASH256 on the locking script.
|
|
207
|
+
require(
|
|
208
|
+
fundingOutputHash.toBytes32() == expectedScript.hash256(),
|
|
209
|
+
"Wrong 32-byte script hash"
|
|
99
210
|
);
|
|
211
|
+
} else {
|
|
212
|
+
revert("Wrong script hash length");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Resulting TX hash is in native Bitcoin little-endian format.
|
|
216
|
+
bytes32 fundingTxHash =
|
|
217
|
+
abi
|
|
218
|
+
.encodePacked(
|
|
219
|
+
fundingTx
|
|
220
|
+
.version,
|
|
221
|
+
fundingTx
|
|
222
|
+
.inputVector,
|
|
223
|
+
fundingTx
|
|
224
|
+
.outputVector,
|
|
225
|
+
fundingTx
|
|
226
|
+
.locktime
|
|
227
|
+
)
|
|
228
|
+
.hash256();
|
|
100
229
|
|
|
101
|
-
DepositInfo storage deposit =
|
|
230
|
+
DepositInfo storage deposit =
|
|
231
|
+
unswept[
|
|
232
|
+
uint256(
|
|
233
|
+
keccak256(
|
|
234
|
+
abi.encodePacked(
|
|
235
|
+
fundingTxHash,
|
|
236
|
+
reveal.fundingOutputIndex
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
];
|
|
102
241
|
require(deposit.revealedAt == 0, "Deposit already revealed");
|
|
103
242
|
|
|
104
|
-
|
|
105
|
-
|
|
243
|
+
bytes8 fundingOutputAmount;
|
|
244
|
+
/* solhint-disable-next-line no-inline-assembly */
|
|
245
|
+
assembly {
|
|
246
|
+
// First 8 bytes (little-endian) of the funding output represents
|
|
247
|
+
// its value. To take the value, we need to jump over the first
|
|
248
|
+
// word determining the array length, load the array, and trim it
|
|
249
|
+
// by putting it to a bytes8.
|
|
250
|
+
fundingOutputAmount := mload(add(fundingOutput, 32))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
deposit.amount = fundingOutputAmount;
|
|
254
|
+
deposit.depositor = reveal.depositor;
|
|
106
255
|
/* solhint-disable-next-line not-rely-on-time */
|
|
107
256
|
deposit.revealedAt = uint32(block.timestamp);
|
|
257
|
+
deposit.vault = reveal.vault;
|
|
108
258
|
|
|
109
259
|
emit DepositRevealed(
|
|
110
|
-
depositId,
|
|
111
260
|
fundingTxHash,
|
|
112
|
-
fundingOutputIndex,
|
|
113
|
-
|
|
114
|
-
blindingFactor,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
261
|
+
reveal.fundingOutputIndex,
|
|
262
|
+
reveal.depositor,
|
|
263
|
+
reveal.blindingFactor,
|
|
264
|
+
reveal.walletPubKeyHash,
|
|
265
|
+
reveal.refundPubKeyHash,
|
|
266
|
+
reveal.refundLocktime
|
|
118
267
|
);
|
|
119
268
|
}
|
|
120
269
|
|
|
@@ -125,45 +274,37 @@ contract Bridge {
|
|
|
125
274
|
/// The function is performing Bank balance updates by first
|
|
126
275
|
/// computing the Bitcoin fee for the sweep transaction. The fee is
|
|
127
276
|
/// divided evenly between all swept deposits. Each depositor
|
|
128
|
-
/// receives a balance in the bank equal to the amount
|
|
129
|
-
///
|
|
277
|
+
/// receives a balance in the bank equal to the amount inferred
|
|
278
|
+
/// during the reveal transaction, minus their fee share.
|
|
130
279
|
///
|
|
131
280
|
/// It is possible to prove the given sweep only one time.
|
|
132
|
-
/// @param
|
|
133
|
-
/// @param
|
|
134
|
-
///
|
|
135
|
-
/// @param txOutput Single sweep transaction output
|
|
136
|
-
/// @param txLocktime Final 4 bytes of the transaction
|
|
137
|
-
/// @param merkleProof The merkle proof of transaction inclusion in a block
|
|
138
|
-
/// @param txIndexInBlock Transaction index in the block (0-indexed)
|
|
281
|
+
/// @param sweepTx Bitcoin sweep transaction data.
|
|
282
|
+
/// @param merkleProof The merkle proof of transaction inclusion in a block.
|
|
283
|
+
/// @param txIndexInBlock Transaction index in the block (0-indexed).
|
|
139
284
|
/// @param bitcoinHeaders Single bytestring of 80-byte bitcoin headers,
|
|
140
|
-
/// lowest height first
|
|
285
|
+
/// lowest height first.
|
|
141
286
|
function sweep(
|
|
142
|
-
|
|
143
|
-
bytes memory txInputVector,
|
|
144
|
-
bytes memory txOutput,
|
|
145
|
-
bytes4 txLocktime,
|
|
287
|
+
TxInfo calldata sweepTx,
|
|
146
288
|
bytes memory merkleProof,
|
|
147
289
|
uint256 txIndexInBlock,
|
|
148
290
|
bytes memory bitcoinHeaders
|
|
149
291
|
) external {
|
|
150
|
-
// TODO We need to read `fundingTxHash`, `fundingOutputIndex`
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// amount to update their Bank balance, minus fee.
|
|
292
|
+
// TODO We need to read `fundingTxHash`, `fundingOutputIndex` from
|
|
293
|
+
// `sweepTx.inputVector`. We then hash them to obtain deposit
|
|
294
|
+
// identifier and read DepositInfo. From DepositInfo we know what
|
|
295
|
+
// amount was inferred during deposit reveal transaction and we
|
|
296
|
+
// use that amount to update their Bank balance, minus fee.
|
|
156
297
|
//
|
|
157
298
|
// TODO We need to validate if the sum in the output minus the
|
|
158
299
|
// amount from the previous wallet balance input minus fees is
|
|
159
300
|
// equal to the amount by which Bank balances were increased.
|
|
160
301
|
//
|
|
161
|
-
// TODO We need to validate
|
|
162
|
-
// transferred away from the wallet before increasing
|
|
163
|
-
// the bank.
|
|
302
|
+
// TODO We need to validate `sweepTx.outputVector` to see if the balance
|
|
303
|
+
// was not transferred away from the wallet before increasing
|
|
304
|
+
// balances in the bank.
|
|
164
305
|
//
|
|
165
306
|
// TODO Delete deposit from unswept mapping or mark it as swept
|
|
166
|
-
// depending on the gas costs.
|
|
307
|
+
// depending on the gas costs. Alternatively, do not allow to
|
|
167
308
|
// use the same TX input vector twice. Sweep should be provable
|
|
168
309
|
// only one time.
|
|
169
310
|
}
|
|
@@ -173,4 +314,5 @@ contract Bridge {
|
|
|
173
314
|
// an incorrect amount revealed. We need to provide a function for honest
|
|
174
315
|
// depositors, next to sweep, to prove their swept balances on Ethereum
|
|
175
316
|
// selectively, based on deposits they have earlier received.
|
|
317
|
+
// (UPDATE PR #90: Is it still the case since amounts are inferred?)
|
|
176
318
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keep-network/tbtc-v2",
|
|
3
|
-
"version": "0.1.1-dev.
|
|
3
|
+
"version": "0.1.1-dev.6+main.68e9da2c1b826725378fc2c33c2067b6c024a26e",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"artifacts/",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"prepublishOnly": "./scripts/prepare-artifacts.sh --network $npm_config_network"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@keep-network/bitcoin-spv-sol": "3.1.0-solc-0.8",
|
|
29
30
|
"@keep-network/tbtc": ">1.1.2-dev <1.1.2-pre",
|
|
30
31
|
"@openzeppelin/contracts": "^4.1.0",
|
|
31
32
|
"@tenderly/hardhat-tenderly": "^1.0.12",
|