-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathBeaconChainProofs.sol
297 lines (236 loc) · 15 KB
/
BeaconChainProofs.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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;
import "./Merkle.sol";
import "../libraries/Endian.sol";
//Utility library for parsing and PHASE0 beacon chain block headers
//SSZ Spec: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization
//BeaconBlockHeader Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
//BeaconState Spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconstate
library BeaconChainProofs {
// constants are the number of fields and the heights of the different merkle trees used in merkleizing beacon chain containers
uint256 internal constant NUM_BEACON_BLOCK_HEADER_FIELDS = 5;
uint256 internal constant BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT = 3;
uint256 internal constant NUM_BEACON_BLOCK_BODY_FIELDS = 11;
uint256 internal constant BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT = 4;
uint256 internal constant NUM_BEACON_STATE_FIELDS = 21;
uint256 internal constant BEACON_STATE_FIELD_TREE_HEIGHT = 5;
uint256 internal constant NUM_ETH1_DATA_FIELDS = 3;
uint256 internal constant ETH1_DATA_FIELD_TREE_HEIGHT = 2;
uint256 internal constant NUM_VALIDATOR_FIELDS = 8;
uint256 internal constant VALIDATOR_FIELD_TREE_HEIGHT = 3;
uint256 internal constant NUM_EXECUTION_PAYLOAD_HEADER_FIELDS = 15;
uint256 internal constant EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT = 4;
uint256 internal constant NUM_EXECUTION_PAYLOAD_FIELDS = 15;
uint256 internal constant EXECUTION_PAYLOAD_FIELD_TREE_HEIGHT = 4;
// HISTORICAL_ROOTS_LIMIT = 2**24, so tree height is 24
uint256 internal constant HISTORICAL_ROOTS_TREE_HEIGHT = 24;
// HISTORICAL_BATCH is root of state_roots and block_root, so number of leaves = 2^1
uint256 internal constant HISTORICAL_BATCH_TREE_HEIGHT = 1;
// SLOTS_PER_HISTORICAL_ROOT = 2**13, so tree height is 13
uint256 internal constant STATE_ROOTS_TREE_HEIGHT = 13;
uint256 internal constant BLOCK_ROOTS_TREE_HEIGHT = 13;
uint256 internal constant NUM_WITHDRAWAL_FIELDS = 4;
// tree height for hash tree of an individual withdrawal container
uint256 internal constant WITHDRAWAL_FIELD_TREE_HEIGHT = 2;
uint256 internal constant VALIDATOR_TREE_HEIGHT = 40;
//refer to the eigenlayer-cli proof library. Despite being the same dimensions as the validator tree, the balance tree is merkleized differently
uint256 internal constant BALANCE_TREE_HEIGHT = 38;
// MAX_WITHDRAWALS_PER_PAYLOAD = 2**4, making tree height = 4
uint256 internal constant WITHDRAWALS_TREE_HEIGHT = 4;
//in beacon block body
uint256 internal constant EXECUTION_PAYLOAD_INDEX = 9;
// in beacon block header
uint256 internal constant STATE_ROOT_INDEX = 3;
uint256 internal constant PROPOSER_INDEX_INDEX = 1;
uint256 internal constant SLOT_INDEX = 0;
uint256 internal constant BODY_ROOT_INDEX = 4;
// in beacon state
uint256 internal constant STATE_ROOTS_INDEX = 6;
uint256 internal constant BLOCK_ROOTS_INDEX = 5;
uint256 internal constant HISTORICAL_ROOTS_INDEX = 7;
uint256 internal constant ETH_1_ROOT_INDEX = 8;
uint256 internal constant VALIDATOR_TREE_ROOT_INDEX = 11;
uint256 internal constant BALANCE_INDEX = 12;
uint256 internal constant EXECUTION_PAYLOAD_HEADER_INDEX = 24;
uint256 internal constant HISTORICAL_BATCH_STATE_ROOT_INDEX = 1;
// in validator
uint256 internal constant VALIDATOR_WITHDRAWAL_CREDENTIALS_INDEX = 1;
uint256 internal constant VALIDATOR_BALANCE_INDEX = 2;
uint256 internal constant VALIDATOR_SLASHED_INDEX = 3;
uint256 internal constant VALIDATOR_WITHDRAWABLE_EPOCH_INDEX = 7;
// in exection payload header
uint256 internal constant BLOCK_NUMBER_INDEX = 6;
uint256 internal constant WITHDRAWALS_ROOT_INDEX = 14;
//in execution payload
uint256 internal constant WITHDRAWALS_INDEX = 14;
// in withdrawal
uint256 internal constant WITHDRAWAL_VALIDATOR_INDEX_INDEX = 1;
uint256 internal constant WITHDRAWAL_VALIDATOR_AMOUNT_INDEX = 3;
//In historicalBatch
uint256 internal constant HISTORICALBATCH_STATEROOTS_INDEX = 1;
//Misc Constants
uint256 internal constant SLOTS_PER_EPOCH = 32;
bytes8 internal constant UINT64_MASK = 0xffffffffffffffff;
struct WithdrawalProofs {
bytes blockHeaderProof;
bytes withdrawalProof;
bytes slotProof;
bytes executionPayloadProof;
bytes blockNumberProof;
uint64 blockHeaderRootIndex;
uint64 withdrawalIndex;
bytes32 blockHeaderRoot;
bytes32 blockBodyRoot;
bytes32 slotRoot;
bytes32 blockNumberRoot;
bytes32 executionPayloadRoot;
}
struct ValidatorFieldsAndBalanceProofs {
bytes validatorFieldsProof;
bytes validatorBalanceProof;
bytes32 balanceRoot;
}
struct ValidatorFieldsProof {
bytes validatorProof;
uint40 validatorIndex;
}
function computePhase0BeaconBlockHeaderRoot(bytes32[NUM_BEACON_BLOCK_HEADER_FIELDS] calldata blockHeaderFields) internal pure returns(bytes32) {
bytes32[] memory paddedHeaderFields = new bytes32[](2**BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT);
for (uint256 i = 0; i < NUM_BEACON_BLOCK_HEADER_FIELDS; ++i) {
paddedHeaderFields[i] = blockHeaderFields[i];
}
return Merkle.merkleizeSha256(paddedHeaderFields);
}
function computePhase0BeaconStateRoot(bytes32[NUM_BEACON_STATE_FIELDS] calldata beaconStateFields) internal pure returns(bytes32) {
bytes32[] memory paddedBeaconStateFields = new bytes32[](2**BEACON_STATE_FIELD_TREE_HEIGHT);
for (uint256 i = 0; i < NUM_BEACON_STATE_FIELDS; ++i) {
paddedBeaconStateFields[i] = beaconStateFields[i];
}
return Merkle.merkleizeSha256(paddedBeaconStateFields);
}
function computePhase0ValidatorRoot(bytes32[NUM_VALIDATOR_FIELDS] calldata validatorFields) internal pure returns(bytes32) {
bytes32[] memory paddedValidatorFields = new bytes32[](2**VALIDATOR_FIELD_TREE_HEIGHT);
for (uint256 i = 0; i < NUM_VALIDATOR_FIELDS; ++i) {
paddedValidatorFields[i] = validatorFields[i];
}
return Merkle.merkleizeSha256(paddedValidatorFields);
}
function computePhase0Eth1DataRoot(bytes32[NUM_ETH1_DATA_FIELDS] calldata eth1DataFields) internal pure returns(bytes32) {
bytes32[] memory paddedEth1DataFields = new bytes32[](2**ETH1_DATA_FIELD_TREE_HEIGHT);
for (uint256 i = 0; i < ETH1_DATA_FIELD_TREE_HEIGHT; ++i) {
paddedEth1DataFields[i] = eth1DataFields[i];
}
return Merkle.merkleizeSha256(paddedEth1DataFields);
}
/**
*
* @notice This function is parses the balanceRoot to get the uint64 balance of a validator. During merkleization of the
* beacon state balance tree, four uint64 values (making 32 bytes) are grouped together and treated as a single leaf in the merkle tree. Thus the
* validatorIndex mod 4 is used to determine which of the four uint64 values to extract from the balanceRoot.
* @param validatorIndex is the index of the validator being proven for.
* @param balanceRoot is the combination of 4 validator balances being proven for.
*/
function getBalanceFromBalanceRoot(uint40 validatorIndex, bytes32 balanceRoot) internal pure returns (uint64) {
uint256 bitShiftAmount = (validatorIndex % 4) * 64;
bytes32 validatorBalanceLittleEndian = bytes32((uint256(balanceRoot) << bitShiftAmount));
uint64 validatorBalance = Endian.fromLittleEndianUint64(validatorBalanceLittleEndian);
return validatorBalance;
}
/**
* @notice This function verifies merkle proofs of the fields of a certain validator against a beacon chain state root
* @param validatorIndex the index of the proven validator
* @param beaconStateRoot is the beacon chain state root to be proven against.
* @param proof is the data used in proving the validator's fields
* @param validatorFields the claimed fields of the validator
*/
function verifyValidatorFields(
uint40 validatorIndex,
bytes32 beaconStateRoot,
bytes calldata proof,
bytes32[] calldata validatorFields
) internal view {
require(validatorFields.length == 2**VALIDATOR_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyValidatorFields: Validator fields has incorrect length");
/**
* Note: the length of the validator merkle proof is BeaconChainProofs.VALIDATOR_TREE_HEIGHT + 1.
* There is an additional layer added by hashing the root with the length of the validator list
*/
require(proof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorFields: Proof has incorrect length");
uint256 index = (VALIDATOR_TREE_ROOT_INDEX << (VALIDATOR_TREE_HEIGHT + 1)) | uint256(validatorIndex);
// merkleize the validatorFields to get the leaf to prove
bytes32 validatorRoot = Merkle.merkleizeSha256(validatorFields);
// verify the proof of the validatorRoot against the beaconStateRoot
require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, validatorRoot, index), "BeaconChainProofs.verifyValidatorFields: Invalid merkle proof");
}
/**
* @notice This function verifies merkle proofs of the balance of a certain validator against a beacon chain state root
* @param validatorIndex the index of the proven validator
* @param beaconStateRoot is the beacon chain state root to be proven against.
* @param proof is the proof of the balance against the beacon chain state root
* @param balanceRoot is the serialized balance used to prove the balance of the validator (refer to `getBalanceFromBalanceRoot` above for detailed explanation)
*/
function verifyValidatorBalance(
uint40 validatorIndex,
bytes32 beaconStateRoot,
bytes calldata proof,
bytes32 balanceRoot
) internal view {
require(proof.length == 32 * ((BALANCE_TREE_HEIGHT + 1) + BEACON_STATE_FIELD_TREE_HEIGHT), "BeaconChainProofs.verifyValidatorBalance: Proof has incorrect length");
/**
* the beacon state's balance list is a list of uint64 values, and these are grouped together in 4s when merkleized.
* Therefore, the index of the balance of a validator is validatorIndex/4
*/
uint256 balanceIndex = uint256(validatorIndex/4);
balanceIndex = (BALANCE_INDEX << (BALANCE_TREE_HEIGHT + 1)) | balanceIndex;
require(Merkle.verifyInclusionSha256(proof, beaconStateRoot, balanceRoot, balanceIndex), "BeaconChainProofs.verifyValidatorBalance: Invalid merkle proof");
}
/**
* @notice This function verifies the slot and the withdrawal fields for a given withdrawal
* @param beaconStateRoot is the beacon chain state root to be proven against.
* @param proofs is the provided set of merkle proofs
* @param withdrawalFields is the serialized withdrawal container to be proven
*/
function verifyWithdrawalProofs(
bytes32 beaconStateRoot,
WithdrawalProofs calldata proofs,
bytes32[] calldata withdrawalFields
) internal view {
require(withdrawalFields.length == 2**WITHDRAWAL_FIELD_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalFields has incorrect length");
require(proofs.blockHeaderRootIndex < 2**BLOCK_ROOTS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: blockRootIndex is too large");
require(proofs.withdrawalIndex < 2**WITHDRAWALS_TREE_HEIGHT, "BeaconChainProofs.verifyWithdrawalProofs: withdrawalIndex is too large");
// verify the block header proof length
require(proofs.blockHeaderProof.length == 32 * (BEACON_STATE_FIELD_TREE_HEIGHT + BLOCK_ROOTS_TREE_HEIGHT),
"BeaconChainProofs.verifyWithdrawalProofs: blockHeaderProof has incorrect length");
require(proofs.withdrawalProof.length == 32 * (EXECUTION_PAYLOAD_HEADER_FIELD_TREE_HEIGHT + WITHDRAWALS_TREE_HEIGHT + 1),
"BeaconChainProofs.verifyWithdrawalProofs: withdrawalProof has incorrect length");
require(proofs.executionPayloadProof.length == 32 * (BEACON_BLOCK_HEADER_FIELD_TREE_HEIGHT + BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT),
"BeaconChainProofs.verifyWithdrawalProofs: executionPayloadProof has incorrect length");
/**
* Computes the block_header_index relative to the beaconStateRoot. It concatenates the indexes of all the
* intermediate root indexes from the bottom of the sub trees (the block header container) to the top of the tree
*/
uint256 blockHeaderIndex = BLOCK_ROOTS_INDEX << (BLOCK_ROOTS_TREE_HEIGHT) | uint256(proofs.blockHeaderRootIndex);
// Verify the blockHeaderRoot against the beaconStateRoot
require(Merkle.verifyInclusionSha256(proofs.blockHeaderProof, beaconStateRoot, proofs.blockHeaderRoot, blockHeaderIndex),
"BeaconChainProofs.verifyWithdrawalProofs: Invalid block header merkle proof");
//Next we verify the slot against the blockHeaderRoot
require(Merkle.verifyInclusionSha256(proofs.slotProof, proofs.blockHeaderRoot, proofs.slotRoot, SLOT_INDEX), "BeaconChainProofs.verifyWithdrawalProofs: Invalid slot merkle proof");
// Next we verify the executionPayloadRoot against the blockHeaderRoot
uint256 executionPayloadIndex = BODY_ROOT_INDEX << (BEACON_BLOCK_BODY_FIELD_TREE_HEIGHT)| EXECUTION_PAYLOAD_INDEX ;
require(Merkle.verifyInclusionSha256(proofs.executionPayloadProof, proofs.blockHeaderRoot, proofs.executionPayloadRoot, executionPayloadIndex),
"BeaconChainProofs.verifyWithdrawalProofs: Invalid executionPayload merkle proof");
// Next we verify the blockNumberRoot against the executionPayload root
require(Merkle.verifyInclusionSha256(proofs.blockNumberProof, proofs.executionPayloadRoot, proofs.blockNumberRoot, BLOCK_NUMBER_INDEX),
"BeaconChainProofs.verifyWithdrawalProofs: Invalid blockNumber merkle proof");
/**
* Next we verify the withdrawal fields against the blockHeaderRoot:
* First we compute the withdrawal_index relative to the blockHeaderRoot by concatenating the indexes of all the
* intermediate root indexes from the bottom of the sub trees (the withdrawal container) to the top, the blockHeaderRoot.
* Then we calculate merkleize the withdrawalFields container to calculate the the withdrawalRoot.
* Finally we verify the withdrawalRoot against the executionPayloadRoot.
*/
uint256 withdrawalIndex = WITHDRAWALS_INDEX << (WITHDRAWALS_TREE_HEIGHT + 1) | uint256(proofs.withdrawalIndex);
bytes32 withdrawalRoot = Merkle.merkleizeSha256(withdrawalFields);
require(Merkle.verifyInclusionSha256(proofs.withdrawalProof, proofs.executionPayloadRoot, withdrawalRoot, withdrawalIndex),
"BeaconChainProofs.verifyWithdrawalProofs: Invalid withdrawal merkle proof");
}
}