forked from BitcoinHEX/contract
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUTXOClaimValidation.sol
275 lines (247 loc) · 8.81 KB
/
UTXOClaimValidation.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
pragma solidity ^0.5.7;
import "./GlobalsAndUtility.sol";
import "../node_modules/openzeppelin-solidity/contracts/cryptography/MerkleProof.sol";
contract UTXOClaimValidation is GlobalsAndUtility {
/**
* @dev PUBLIC FACING: Verify a BTC address and balance are unclaimed and part of the Merkle tree
* @param btcAddr Bitcoin address (binary; no base58-check encoding)
* @param rawSatoshis Raw BTC address balance in Satoshis
* @param proof Merkle tree proof
* @return True if can be claimed
*/
function canClaimBtcAddress(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
external
view
returns (bool)
{
require(_getCurrentDay() < CLAIM_PHASE_DAYS, "HEX: Claim phase has ended");
/* Don't need to check Merkle proof if UTXO BTC address has already been claimed */
if (claimedBtcAddresses[btcAddr]) {
return false;
}
/* Verify the Merkle tree proof */
return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
}
/**
* @dev PUBLIC FACING: Verify a BTC address and balance are part of the Merkle tree
* @param btcAddr Bitcoin address (binary; no base58-check encoding)
* @param rawSatoshis Raw BTC address balance in Satoshis
* @param proof Merkle tree proof
* @return True if valid
*/
function btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] calldata proof)
external
pure
returns (bool)
{
return _btcAddressIsValid(btcAddr, rawSatoshis, proof);
}
/**
* @dev PUBLIC FACING: Verify a Merkle proof using the UTXO Merkle tree
* @param merkleLeaf Leaf asserted to be present in the Merkle tree
* @param proof Generated Merkle tree proof
* @return True if valid
*/
function merkleProofIsValid(bytes32 merkleLeaf, bytes32[] calldata proof)
external
pure
returns (bool)
{
return _merkleProofIsValid(merkleLeaf, proof);
}
/**
* @dev Verify that a Bitcoin signature matches the claim message containing
* the Ethereum address
* @param claimToAddr Eth address within the signed claim message
* @param pubKeyX First half of uncompressed ECDSA public key
* @param pubKeyY Second half of uncompressed ECDSA public key
* @param v v parameter of ECDSA signature
* @param r r parameter of ECDSA signature
* @param s s parameter of ECDSA signature
* @return True if matching
*/
function signatureMatchesClaim(
address claimToAddr,
bytes32 pubKeyX,
bytes32 pubKeyY,
uint8 v,
bytes32 r,
bytes32 s
)
public
pure
returns (bool)
{
require(v >= 27 && v <= 30, "HEX: v invalid");
/*
ecrecover() returns an Eth address rather than a public key, so
we must do the same to compare.
*/
address pubKeyEthAddr = pubKeyToEthAddress(pubKeyX, pubKeyY);
/* Try matching the most likely type of claim message */
bytes32 messageHash = _hash256(_createStandardClaimMessage(claimToAddr));
if (ecrecover(messageHash, v, r, s) == pubKeyEthAddr) {
return true;
}
/* Otherwise try the matching the legacy claim message as a fallback */
messageHash = _hash256(_createLegacyClaimMessage(claimToAddr));
return ecrecover(messageHash, v, r, s) == pubKeyEthAddr;
}
/**
* @dev Derive an Ethereum address from an ECDSA public key
* @param pubKeyX First half of uncompressed ECDSA public key
* @param pubKeyY Second half of uncompressed ECDSA public key
* @return Derived Ethereum address
*/
function pubKeyToEthAddress(bytes32 pubKeyX, bytes32 pubKeyY)
public
pure
returns (address)
{
return address(uint160(uint256(keccak256(abi.encodePacked(pubKeyX, pubKeyY)))));
}
/**
* @dev Derive a Bitcoin address from an ECDSA public key
* @param pubKeyX First half of uncompressed ECDSA public key
* @param pubKeyY Second half of uncompressed ECDSA public key
* @param addrType Type of BTC address to derive from the public key
* @return Derived Bitcoin address (binary; no base58-check encoding)
*/
function pubKeyToBtcAddress(bytes32 pubKeyX, bytes32 pubKeyY, uint8 addrType)
public
pure
returns (bytes20)
{
require(addrType < BTC_ADDR_TYPE_COUNT, "HEX: addrType invalid");
/*
Helpful references:
- https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
- https://github.com/cryptocoinjs/ecurve/blob/master/lib/point.js
*/
uint8 startingByte;
bytes memory pubKey;
if (addrType == BTC_ADDR_TYPE_P2PKH_UNCOMPRESSED) {
/* Uncompressed public key format. */
startingByte = 0x04;
pubKey = abi.encodePacked(startingByte, pubKeyX, pubKeyY);
} else {
/* Compressed public key format. */
startingByte = (pubKeyY[31] & 0x01) == 0 ? 0x02 : 0x03;
pubKey = abi.encodePacked(startingByte, pubKeyX);
}
bytes20 pubKeyHash = _hash160(pubKey);
if (addrType != BTC_ADDR_TYPE_P2WPKH_IN_P2SH) {
return pubKeyHash;
}
return _hash160(abi.encodePacked(hex"0014", pubKeyHash));
}
/**
* @dev Verify a BTC address and balance are part of the Merkle tree
* @param btcAddr Bitcoin address (binary; no base58-check encoding)
* @param rawSatoshis Raw BTC address balance in Satoshis
* @param proof Merkle tree proof
* @return True if valid
*/
function _btcAddressIsValid(bytes20 btcAddr, uint256 rawSatoshis, bytes32[] memory proof)
internal
pure
returns (bool)
{
/* Calculate the 32 byte Merkle leaf associated with this BTC address and balance */
bytes32 merkleLeaf = bytes32(btcAddr) | bytes32(rawSatoshis);
/* Verify the Merkle tree proof */
return _merkleProofIsValid(merkleLeaf, proof);
}
/**
* @dev Verify a Merkle proof using the UTXO Merkle tree
* @param merkleLeaf Leaf asserted to be present in the Merkle tree
* @param proof Generated Merkle tree proof
* @return True if valid
*/
function _merkleProofIsValid(bytes32 merkleLeaf, bytes32[] memory proof)
private
pure
returns (bool)
{
return MerkleProof.verify(proof, MERKLE_TREE_ROOT, merkleLeaf);
}
/**
* @dev Creates a HEX claim message from an Ethereum address
* @param claimToAddr Destination Eth address to credit the claimed Hearts
* @return Standard claim message
*/
function _createStandardClaimMessage(address claimToAddr)
private
pure
returns (bytes memory)
{
return abi.encodePacked(
uint8(24),
bytes24("Bitcoin Signed Message:\n"),
uint8(15 + ETH_ADDRESS_HEX_LEN),
bytes15("Claim_HEX_to_0x"),
_createHexStringFromEthAddress(claimToAddr)
);
}
/**
* @dev Creates a BitcoinHEX claim message from an Ethereum address
* @param claimToAddr Destination Eth address to credit the claimed Hearts
* @return Legacy claim message
*/
function _createLegacyClaimMessage(address claimToAddr)
private
pure
returns (bytes memory)
{
return abi.encodePacked(
uint8(24),
bytes24("Bitcoin Signed Message:\n"),
uint8(22 + ETH_ADDRESS_HEX_LEN),
bytes22("Claim_BitcoinHEX_to_0x"),
_createHexStringFromEthAddress(claimToAddr)
);
}
/**
* @dev Creates a lowercase hex string from an Ethereum address
* @param ethAddr Eth address to convert
* @return Hex string of Eth address
*/
function _createHexStringFromEthAddress(address ethAddr)
private
pure
returns (bytes memory hexStr)
{
hexStr = new bytes(ETH_ADDRESS_HEX_LEN);
uint256 offset = 0;
for (uint256 i = 0; i < ETH_ADDRESS_BYTE_LEN; i++) {
uint8 b = uint8(bytes20(ethAddr)[i]);
hexStr[offset++] = HEX_DIGITS[b >> 4];
hexStr[offset++] = HEX_DIGITS[b & 0x0f];
}
return hexStr;
}
/**
* @dev sha256(sha256(data))
* @param data Data to be hashed
* @return 32-byte hash
*/
function _hash256(bytes memory data)
private
pure
returns (bytes32)
{
return sha256(abi.encodePacked(sha256(data)));
}
/**
* @dev ripemd160(sha256(data))
* @param data Data to be hashed
* @return 20-byte hash
*/
function _hash160(bytes memory data)
private
pure
returns (bytes20)
{
return ripemd160(abi.encodePacked(sha256(data)));
}
}