-
Notifications
You must be signed in to change notification settings - Fork 8
/
BeaconLightClient.sol
312 lines (284 loc) · 13.4 KB
/
BeaconLightClient.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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// This file is part of Darwinia.
// Copyright (C) 2018-2022 Darwinia Network
// SPDX-License-Identifier: GPL-3.0
//
// Darwinia is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Darwinia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Darwinia. If not, see <https://www.gnu.org/licenses/>.
//
// Etherum beacon light client.
// Current arthitecture diverges from spec's proposed updated splitting them into:
// - Finalized header updates: To import a recent finalized header signed by a known sync committee by `import_finalized_header`.
// - Sync period updates: To advance to the next committee by `import_next_sync_committee`.
//
// To stay synced to the current sync period it needs:
// - Get sync_period_update at least once per period.
//
// To get light-client best finalized update at period N:
// - Fetch best finalized block's sync_aggregate_header in period N
// - Fetch parent_block/attested_block by sync_aggregate_header's parent_root
// - Fetch finalized_checkpoint_root and finalized_checkpoint_root_witness in attested_block
// - Fetch finalized_header by finalized_checkpoint_root
//
// - sync_aggregate -> parent_block/attested_block -> finalized_checkpoint -> finalized_header
//
// To get light-client sync period update at period N:
// - Fetch the finalized_header in light-client
// - Fetch the finalized_block by finalized_header.slot
// - Fetch next_sync_committee and next_sync_committee_witness in finalized_block
//
// - finalized_header -> next_sync_committee
//
// ```
// Finalized Block Sync
// Checkpoint Header Aggreate
// ----------------------|-----------------------|-------|---------> time
// <--------------------- <----
// finalizes signs
// ```
//
// To initialize, it needs:
// - BLS verify contract
// - Trust finalized_header
// - current_sync_committee of the trust finalized_header
// - genesis_validators_root of genesis state
//
// When to trigger a committee update sync:
//
// period 0 period 1 period 2
// -|----------------|----------------|----------------|-> time
// | now
// - active current_sync_committee
// - known next_sync_committee, signed by current_sync_committee
//
//
// next_sync_committee can be imported at any time of the period, not strictly at the period borders.
// - No need to query for period 0 next_sync_committee until the end of period 0
// - After the import next_sync_committee of period 0, populate period 1's committee
//
// Inspired: https://github.com/ethereum/annotated-spec/blob/master/altair/sync-protocol.md
pragma solidity 0.8.17;
import "../../utils/Bitfield.sol";
import "../../spec/BeaconLightClientUpdate.sol";
interface IBLS {
function fast_aggregate_verify(
bytes[] calldata pubkeys,
bytes calldata message,
bytes calldata signature
) external pure returns (bool);
}
contract BeaconLightClient is BeaconLightClientUpdate, Bitfield {
/// @dev Beacon block header that is finalized
BeaconBlockHeader public finalized_header;
/// @dev Sync committees corresponding to the header
/// sync_committee_perid => sync_committee_root
mapping (uint64 => bytes32) public sync_committee_roots;
/// @dev bls12-381 precompile address(0x0800)
address private immutable BLS_PRECOMPILE;
/// @dev Beacon chain genesis validators root
bytes32 public immutable GENESIS_VALIDATORS_ROOT;
// A bellatrix beacon state has 25 fields, with a depth of 5.
// | field | gindex | depth |
// | ----------------------------------- | ------ | ----- |
// | next_sync_committee | 55 | 5 |
// | finalized_checkpoint_root | 105 | 6 |
uint64 constant private NEXT_SYNC_COMMITTEE_INDEX = 55;
uint64 constant private NEXT_SYNC_COMMITTEE_DEPTH = 5;
uint64 constant private FINALIZED_CHECKPOINT_ROOT_INDEX = 105;
uint64 constant private FINALIZED_CHECKPOINT_ROOT_DEPTH = 6;
uint64 constant private SLOTS_PER_EPOCH = 32;
uint64 constant private EPOCHS_PER_SYNC_COMMITTEE_PERIOD = 256;
bytes4 constant private DOMAIN_SYNC_COMMITTEE = 0x07000000;
event FinalizedHeaderImported(BeaconBlockHeader finalized_header);
event NextSyncCommitteeImported(uint64 indexed period, bytes32 indexed next_sync_committee_root);
constructor(
address _bls,
uint64 _slot,
uint64 _proposer_index,
bytes32 _parent_root,
bytes32 _state_root,
bytes32 _body_root,
bytes32 _current_sync_committee_hash,
bytes32 _genesis_validators_root
) {
BLS_PRECOMPILE = _bls;
finalized_header = BeaconBlockHeader(_slot, _proposer_index, _parent_root, _state_root, _body_root);
sync_committee_roots[compute_sync_committee_period(_slot)] = _current_sync_committee_hash;
GENESIS_VALIDATORS_ROOT = _genesis_validators_root;
}
/// @dev Return beacon light client finalized header's body root
/// @return body root
function body_root() public view returns (bytes32) {
return finalized_header.body_root;
}
/// @dev follow beacon api: /beacon/light_client/updates/?start_period={period}&count={count}
function import_next_sync_committee(
FinalizedHeaderUpdate calldata header_update,
SyncCommitteePeriodUpdate calldata sc_update
) external {
require(is_supermajority(header_update.sync_aggregate.sync_committee_bits), "!supermajor");
require(header_update.signature_slot > header_update.attested_header.slot &&
header_update.attested_header.slot >= header_update.finalized_header.slot,
"!skip");
require(verify_finalized_header(
header_update.finalized_header,
header_update.finality_branch,
header_update.attested_header.state_root),
"!finalized_header"
);
uint64 finalized_period = compute_sync_committee_period(header_update.finalized_header.slot);
uint64 signature_period = compute_sync_committee_period(header_update.signature_slot);
require(signature_period == finalized_period, "!period");
bytes32 singature_sync_committee_root = sync_committee_roots[signature_period];
require(singature_sync_committee_root != bytes32(0), "!missing");
require(singature_sync_committee_root == hash_tree_root(header_update.signature_sync_committee), "!sync_committee");
require(verify_signed_header(
header_update.sync_aggregate,
header_update.signature_sync_committee,
header_update.fork_version,
header_update.attested_header),
"!sign");
if (header_update.finalized_header.slot > finalized_header.slot) {
finalized_header = header_update.finalized_header;
emit FinalizedHeaderImported(header_update.finalized_header);
}
bytes32 next_sync_committee_root = hash_tree_root(sc_update.next_sync_committee);
require(verify_next_sync_committee(
next_sync_committee_root,
sc_update.next_sync_committee_branch,
header_update.attested_header.state_root),
"!next_sync_committee"
);
uint64 next_period = signature_period + 1;
require(sync_committee_roots[next_period] == bytes32(0), "imported");
sync_committee_roots[next_period] = next_sync_committee_root;
emit NextSyncCommitteeImported(next_period, next_sync_committee_root);
}
/// @dev follow beacon api: /eth/v1/beacon/light_client/finality_update/
function import_finalized_header(FinalizedHeaderUpdate calldata update) external {
require(is_supermajority(update.sync_aggregate.sync_committee_bits), "!supermajor");
require(update.signature_slot > update.attested_header.slot &&
update.attested_header.slot >= update.finalized_header.slot,
"!skip");
require(verify_finalized_header(
update.finalized_header,
update.finality_branch,
update.attested_header.state_root),
"!finalized_header"
);
uint64 finalized_period = compute_sync_committee_period(finalized_header.slot);
uint64 signature_period = compute_sync_committee_period(update.signature_slot);
require(signature_period == finalized_period ||
signature_period == finalized_period + 1,
"!signature_period");
bytes32 singature_sync_committee_root = sync_committee_roots[signature_period];
require(singature_sync_committee_root != bytes32(0), "!missing");
require(singature_sync_committee_root == hash_tree_root(update.signature_sync_committee), "!sync_committee");
require(verify_signed_header(
update.sync_aggregate,
update.signature_sync_committee,
update.fork_version,
update.attested_header),
"!sign");
require(update.finalized_header.slot > finalized_header.slot, "!new");
finalized_header = update.finalized_header;
emit FinalizedHeaderImported(update.finalized_header);
}
function verify_signed_header(
SyncAggregate calldata sync_aggregate,
SyncCommittee calldata sync_committee,
bytes4 fork_version,
BeaconBlockHeader calldata header
) internal view returns (bool) {
// Verify sync committee aggregate signature
uint participants = sum(sync_aggregate.sync_committee_bits);
bytes[] memory participant_pubkeys = new bytes[](participants);
uint64 n = 0;
unchecked {
for (uint64 i = 0; i < SYNC_COMMITTEE_SIZE; ++i) {
uint index = i >> 8;
uint sindex = i / 8 % 32;
uint offset = i % 8;
if (uint8(sync_aggregate.sync_committee_bits[index][sindex]) >> offset & 1 == 1) {
participant_pubkeys[n++] = sync_committee.pubkeys[i];
}
}
}
bytes32 domain = compute_domain(DOMAIN_SYNC_COMMITTEE, fork_version, GENESIS_VALIDATORS_ROOT);
bytes32 signing_root = compute_signing_root(header, domain);
bytes memory message = abi.encodePacked(signing_root);
bytes memory signature = sync_aggregate.sync_committee_signature;
require(signature.length == BLSSIGNATURE_LENGTH, "!signature");
return fast_aggregate_verify(participant_pubkeys, message, signature);
}
function verify_finalized_header(
BeaconBlockHeader calldata header,
bytes32[] calldata finality_branch,
bytes32 attested_header_state_root
) internal pure returns (bool) {
require(finality_branch.length == FINALIZED_CHECKPOINT_ROOT_DEPTH, "!finality_branch");
bytes32 header_root = hash_tree_root(header);
return is_valid_merkle_branch(
header_root,
finality_branch,
FINALIZED_CHECKPOINT_ROOT_DEPTH,
FINALIZED_CHECKPOINT_ROOT_INDEX,
attested_header_state_root
);
}
function verify_next_sync_committee(
bytes32 next_sync_committee_root,
bytes32[] calldata next_sync_committee_branch,
bytes32 header_state_root
) internal pure returns (bool) {
require(next_sync_committee_branch.length == NEXT_SYNC_COMMITTEE_DEPTH, "!next_sync_committee_branch");
return is_valid_merkle_branch(
next_sync_committee_root,
next_sync_committee_branch,
NEXT_SYNC_COMMITTEE_DEPTH,
NEXT_SYNC_COMMITTEE_INDEX,
header_state_root
);
}
function is_supermajority(bytes32[2] calldata sync_committee_bits) internal pure returns (bool) {
return sum(sync_committee_bits) * 3 >= SYNC_COMMITTEE_SIZE * 2;
}
function fast_aggregate_verify(bytes[] memory pubkeys, bytes memory message, bytes memory signature) internal view returns (bool valid) {
bytes memory input = abi.encodeWithSelector(
IBLS.fast_aggregate_verify.selector,
pubkeys,
message,
signature
);
(bool ok, bytes memory out) = BLS_PRECOMPILE.staticcall(input);
if (ok) {
if (out.length == 32) {
valid = abi.decode(out, (bool));
}
} else {
if (out.length > 0) {
assembly ("memory-safe") {
let returndata_size := mload(out)
revert(add(32, out), returndata_size)
}
} else {
revert("!verify");
}
}
}
function compute_sync_committee_period(uint64 slot) internal pure returns (uint64) {
return slot / SLOTS_PER_EPOCH / EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
}
function sum(bytes32[2] memory x) internal pure returns (uint256) {
return countSetBits(uint(x[0])) + countSetBits(uint(x[1]));
}
}