@lodestar/light-client 1.35.0-dev.f80d2d52da → 1.35.0-dev.fcf8d024ea
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/lib/events.d.ts +1 -5
- package/lib/events.d.ts.map +1 -0
- package/lib/events.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +10 -5
- package/lib/index.js.map +1 -1
- package/lib/spec/index.d.ts +1 -1
- package/lib/spec/index.d.ts.map +1 -0
- package/lib/spec/index.js +3 -0
- package/lib/spec/index.js.map +1 -1
- package/lib/spec/isBetterUpdate.d.ts.map +1 -0
- package/lib/spec/processLightClientUpdate.d.ts.map +1 -0
- package/lib/spec/store.d.ts.map +1 -0
- package/lib/spec/store.js +7 -3
- package/lib/spec/store.js.map +1 -1
- package/lib/spec/utils.d.ts.map +1 -0
- package/lib/spec/utils.js.map +1 -1
- package/lib/spec/validateLightClientBootstrap.d.ts.map +1 -0
- package/lib/spec/validateLightClientUpdate.d.ts.map +1 -0
- package/lib/transport/index.d.ts.map +1 -0
- package/lib/transport/interface.d.ts.map +1 -0
- package/lib/transport/rest.d.ts +1 -1
- package/lib/transport/rest.d.ts.map +1 -0
- package/lib/transport/rest.js +5 -4
- package/lib/transport/rest.js.map +1 -1
- package/lib/transport.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/chunkify.d.ts.map +1 -0
- package/lib/utils/clock.d.ts.map +1 -0
- package/lib/utils/domain.d.ts.map +1 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/logger.d.ts.map +1 -0
- package/lib/utils/map.d.ts.map +1 -0
- package/lib/utils/normalizeMerkleBranch.d.ts.map +1 -0
- package/lib/utils/update.d.ts.map +1 -0
- package/lib/utils/utils.d.ts.map +1 -0
- package/lib/utils/verifyMerkleBranch.d.ts.map +1 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/validation.d.ts.map +1 -0
- package/package.json +16 -18
- package/src/events.ts +17 -0
- package/src/index.ts +340 -0
- package/src/spec/index.ts +71 -0
- package/src/spec/isBetterUpdate.ts +94 -0
- package/src/spec/processLightClientUpdate.ts +119 -0
- package/src/spec/store.ts +105 -0
- package/src/spec/utils.ts +266 -0
- package/src/spec/validateLightClientBootstrap.ts +41 -0
- package/src/spec/validateLightClientUpdate.ts +154 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/interface.ts +37 -0
- package/src/transport/rest.ts +89 -0
- package/src/transport.ts +2 -0
- package/src/types.ts +20 -0
- package/src/utils/api.ts +19 -0
- package/src/utils/chunkify.ts +26 -0
- package/src/utils/clock.ts +45 -0
- package/src/utils/domain.ts +44 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +29 -0
- package/src/utils/map.ts +20 -0
- package/src/utils/normalizeMerkleBranch.ts +15 -0
- package/src/utils/update.ts +30 -0
- package/src/utils/utils.ts +95 -0
- package/src/utils/verifyMerkleBranch.ts +29 -0
- package/src/utils.ts +2 -0
- package/src/validation.ts +201 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import bls from "@chainsafe/bls";
|
|
2
|
+
import type {PublicKey, Signature} from "@chainsafe/bls/types";
|
|
3
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
4
|
+
import {
|
|
5
|
+
DOMAIN_SYNC_COMMITTEE,
|
|
6
|
+
FINALIZED_ROOT_DEPTH,
|
|
7
|
+
FINALIZED_ROOT_DEPTH_ELECTRA,
|
|
8
|
+
FINALIZED_ROOT_INDEX,
|
|
9
|
+
FINALIZED_ROOT_INDEX_ELECTRA,
|
|
10
|
+
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
11
|
+
NEXT_SYNC_COMMITTEE_DEPTH,
|
|
12
|
+
NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA,
|
|
13
|
+
NEXT_SYNC_COMMITTEE_INDEX,
|
|
14
|
+
NEXT_SYNC_COMMITTEE_INDEX_ELECTRA,
|
|
15
|
+
} from "@lodestar/params";
|
|
16
|
+
import {
|
|
17
|
+
LightClientFinalityUpdate,
|
|
18
|
+
LightClientUpdate,
|
|
19
|
+
Root,
|
|
20
|
+
Slot,
|
|
21
|
+
altair,
|
|
22
|
+
isELectraLightClientFinalityUpdate,
|
|
23
|
+
isElectraLightClientUpdate,
|
|
24
|
+
ssz,
|
|
25
|
+
} from "@lodestar/types";
|
|
26
|
+
import {SyncCommitteeFast} from "./types.js";
|
|
27
|
+
import {computeSyncPeriodAtSlot} from "./utils/clock.js";
|
|
28
|
+
import {assertZeroHashes, getParticipantPubkeys, isEmptyHeader} from "./utils/utils.js";
|
|
29
|
+
import {isValidMerkleBranch} from "./utils/verifyMerkleBranch.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param config the beacon node config
|
|
34
|
+
* @param syncCommittee the sync committee update
|
|
35
|
+
* @param update the light client update for validation
|
|
36
|
+
*/
|
|
37
|
+
export function assertValidLightClientUpdate(
|
|
38
|
+
config: BeaconConfig,
|
|
39
|
+
syncCommittee: SyncCommitteeFast,
|
|
40
|
+
update: LightClientUpdate
|
|
41
|
+
): void {
|
|
42
|
+
// DIFF FROM SPEC: An update with the same header.slot can be valid and valuable to the lightclient
|
|
43
|
+
// It may have more consensus and result in a better snapshot whilst not advancing the state
|
|
44
|
+
// ----
|
|
45
|
+
// Verify update slot is larger than snapshot slot
|
|
46
|
+
// if (update.header.slot <= snapshot.header.slot) {
|
|
47
|
+
// throw Error("update slot is less or equal snapshot slot");
|
|
48
|
+
// }
|
|
49
|
+
|
|
50
|
+
// Verify update header root is the finalized root of the finality header, if specified
|
|
51
|
+
const isFinalized = !isEmptyHeader(update.finalizedHeader.beacon);
|
|
52
|
+
if (isFinalized) {
|
|
53
|
+
assertValidFinalityProof(update);
|
|
54
|
+
} else {
|
|
55
|
+
assertZeroHashes(
|
|
56
|
+
update.finalityBranch,
|
|
57
|
+
isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH,
|
|
58
|
+
"finalityBranches"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// DIFF FROM SPEC:
|
|
63
|
+
// The nextSyncCommitteeBranch should be check always not only when updatePeriodIncremented
|
|
64
|
+
// An update may not increase the period but still be stored in validUpdates and be used latter
|
|
65
|
+
assertValidSyncCommitteeProof(update);
|
|
66
|
+
|
|
67
|
+
const {attestedHeader} = update;
|
|
68
|
+
const headerBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(attestedHeader.beacon);
|
|
69
|
+
assertValidSignedHeader(config, syncCommittee, update.syncAggregate, headerBlockRoot, attestedHeader.beacon.slot);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Proof that the state referenced in `update.finalityHeader.stateRoot` includes
|
|
74
|
+
* ```ts
|
|
75
|
+
* state = {
|
|
76
|
+
* finalizedCheckpoint: {
|
|
77
|
+
* root: update.header
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Where `hashTreeRoot(state) == update.finalityHeader.stateRoot`
|
|
83
|
+
*/
|
|
84
|
+
export function assertValidFinalityProof(update: LightClientFinalityUpdate): void {
|
|
85
|
+
const finalizedRootDepth = isELectraLightClientFinalityUpdate(update)
|
|
86
|
+
? FINALIZED_ROOT_DEPTH_ELECTRA
|
|
87
|
+
: FINALIZED_ROOT_DEPTH;
|
|
88
|
+
const finalizedRootIndex = isELectraLightClientFinalityUpdate(update)
|
|
89
|
+
? FINALIZED_ROOT_INDEX_ELECTRA
|
|
90
|
+
: FINALIZED_ROOT_INDEX;
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
!isValidMerkleBranch(
|
|
94
|
+
ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon),
|
|
95
|
+
update.finalityBranch,
|
|
96
|
+
finalizedRootDepth,
|
|
97
|
+
finalizedRootIndex,
|
|
98
|
+
update.attestedHeader.beacon.stateRoot
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
101
|
+
throw Error("Invalid finality header merkle branch");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const updatePeriod = computeSyncPeriodAtSlot(update.attestedHeader.beacon.slot);
|
|
105
|
+
const updateFinalityPeriod = computeSyncPeriodAtSlot(update.finalizedHeader.beacon.slot);
|
|
106
|
+
if (updateFinalityPeriod !== updatePeriod) {
|
|
107
|
+
throw Error(`finalityHeader period ${updateFinalityPeriod} != header period ${updatePeriod}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Proof that the state referenced in `update.header.stateRoot` includes
|
|
113
|
+
* ```ts
|
|
114
|
+
* state = {
|
|
115
|
+
* nextSyncCommittee: update.nextSyncCommittee
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* Where `hashTreeRoot(state) == update.header.stateRoot`
|
|
120
|
+
*/
|
|
121
|
+
export function assertValidSyncCommitteeProof(update: LightClientUpdate): void {
|
|
122
|
+
if (
|
|
123
|
+
!isValidMerkleBranch(
|
|
124
|
+
ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
|
|
125
|
+
update.nextSyncCommitteeBranch,
|
|
126
|
+
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH,
|
|
127
|
+
isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX,
|
|
128
|
+
update.attestedHeader.beacon.stateRoot
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
131
|
+
throw Error("Invalid next sync committee merkle branch");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Assert valid signature for `signedHeader` with provided `syncCommittee`.
|
|
137
|
+
*
|
|
138
|
+
* update.syncCommitteeSignature signs over the block at the previous slot of the state it is included.
|
|
139
|
+
* ```py
|
|
140
|
+
* previous_slot = max(state.slot, Slot(1)) - Slot(1)
|
|
141
|
+
* domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot))
|
|
142
|
+
* signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain)
|
|
143
|
+
* ```
|
|
144
|
+
* Ref: https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/beacon-chain.md#sync-aggregate-processing
|
|
145
|
+
*
|
|
146
|
+
* @param syncCommittee SyncPeriod that signed this update: `computeSyncPeriodAtSlot(update.header.slot) - 1`
|
|
147
|
+
* @param forkVersion ForkVersion that was used to sign the update
|
|
148
|
+
* @param signedHeaderRoot Takes header root instead of the head itself to prevent re-hashing on SSE
|
|
149
|
+
*/
|
|
150
|
+
export function assertValidSignedHeader(
|
|
151
|
+
config: BeaconConfig,
|
|
152
|
+
syncCommittee: SyncCommitteeFast,
|
|
153
|
+
syncAggregate: altair.SyncAggregate,
|
|
154
|
+
signedHeaderRoot: Root,
|
|
155
|
+
signedHeaderSlot: Slot
|
|
156
|
+
): void {
|
|
157
|
+
const participantPubkeys = getParticipantPubkeys(syncCommittee.pubkeys, syncAggregate.syncCommitteeBits);
|
|
158
|
+
|
|
159
|
+
// Verify sync committee has sufficient participants.
|
|
160
|
+
// SyncAggregates included in blocks may have zero participants
|
|
161
|
+
if (participantPubkeys.length < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
|
|
162
|
+
throw Error("Sync committee has not sufficient participants");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const signingRoot = ssz.phase0.SigningData.hashTreeRoot({
|
|
166
|
+
objectRoot: signedHeaderRoot,
|
|
167
|
+
domain: config.getDomain(signedHeaderSlot, DOMAIN_SYNC_COMMITTEE),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (!isValidBlsAggregate(participantPubkeys, signingRoot, syncAggregate.syncCommitteeSignature)) {
|
|
171
|
+
throw Error("Invalid aggregate signature");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Same as BLS.verifyAggregate but with detailed error messages
|
|
177
|
+
*/
|
|
178
|
+
function isValidBlsAggregate(publicKeys: PublicKey[], message: Uint8Array, signature: Uint8Array): boolean {
|
|
179
|
+
let aggPubkey: PublicKey;
|
|
180
|
+
try {
|
|
181
|
+
aggPubkey = bls.PublicKey.aggregate(publicKeys);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
(e as Error).message = `Error aggregating pubkeys: ${(e as Error).message}`;
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let sig: Signature;
|
|
188
|
+
try {
|
|
189
|
+
sig = bls.Signature.fromBytes(signature, undefined, true);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
(e as Error).message = `Error deserializing signature: ${(e as Error).message}`;
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
return sig.verify(aggPubkey, message);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
(e as Error).message = `Error verifying signature: ${(e as Error).message}`;
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
}
|