@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.
Files changed (69) hide show
  1. package/lib/events.d.ts +1 -5
  2. package/lib/events.d.ts.map +1 -0
  3. package/lib/events.js.map +1 -1
  4. package/lib/index.d.ts +1 -1
  5. package/lib/index.d.ts.map +1 -0
  6. package/lib/index.js +10 -5
  7. package/lib/index.js.map +1 -1
  8. package/lib/spec/index.d.ts +1 -1
  9. package/lib/spec/index.d.ts.map +1 -0
  10. package/lib/spec/index.js +3 -0
  11. package/lib/spec/index.js.map +1 -1
  12. package/lib/spec/isBetterUpdate.d.ts.map +1 -0
  13. package/lib/spec/processLightClientUpdate.d.ts.map +1 -0
  14. package/lib/spec/store.d.ts.map +1 -0
  15. package/lib/spec/store.js +7 -3
  16. package/lib/spec/store.js.map +1 -1
  17. package/lib/spec/utils.d.ts.map +1 -0
  18. package/lib/spec/utils.js.map +1 -1
  19. package/lib/spec/validateLightClientBootstrap.d.ts.map +1 -0
  20. package/lib/spec/validateLightClientUpdate.d.ts.map +1 -0
  21. package/lib/transport/index.d.ts.map +1 -0
  22. package/lib/transport/interface.d.ts.map +1 -0
  23. package/lib/transport/rest.d.ts +1 -1
  24. package/lib/transport/rest.d.ts.map +1 -0
  25. package/lib/transport/rest.js +5 -4
  26. package/lib/transport/rest.js.map +1 -1
  27. package/lib/transport.d.ts.map +1 -0
  28. package/lib/types.d.ts.map +1 -0
  29. package/lib/utils/api.d.ts.map +1 -0
  30. package/lib/utils/chunkify.d.ts.map +1 -0
  31. package/lib/utils/clock.d.ts.map +1 -0
  32. package/lib/utils/domain.d.ts.map +1 -0
  33. package/lib/utils/index.d.ts.map +1 -0
  34. package/lib/utils/logger.d.ts.map +1 -0
  35. package/lib/utils/map.d.ts.map +1 -0
  36. package/lib/utils/normalizeMerkleBranch.d.ts.map +1 -0
  37. package/lib/utils/update.d.ts.map +1 -0
  38. package/lib/utils/utils.d.ts.map +1 -0
  39. package/lib/utils/verifyMerkleBranch.d.ts.map +1 -0
  40. package/lib/utils.d.ts.map +1 -0
  41. package/lib/validation.d.ts.map +1 -0
  42. package/package.json +16 -18
  43. package/src/events.ts +17 -0
  44. package/src/index.ts +340 -0
  45. package/src/spec/index.ts +71 -0
  46. package/src/spec/isBetterUpdate.ts +94 -0
  47. package/src/spec/processLightClientUpdate.ts +119 -0
  48. package/src/spec/store.ts +105 -0
  49. package/src/spec/utils.ts +266 -0
  50. package/src/spec/validateLightClientBootstrap.ts +41 -0
  51. package/src/spec/validateLightClientUpdate.ts +154 -0
  52. package/src/transport/index.ts +2 -0
  53. package/src/transport/interface.ts +37 -0
  54. package/src/transport/rest.ts +89 -0
  55. package/src/transport.ts +2 -0
  56. package/src/types.ts +20 -0
  57. package/src/utils/api.ts +19 -0
  58. package/src/utils/chunkify.ts +26 -0
  59. package/src/utils/clock.ts +45 -0
  60. package/src/utils/domain.ts +44 -0
  61. package/src/utils/index.ts +8 -0
  62. package/src/utils/logger.ts +29 -0
  63. package/src/utils/map.ts +20 -0
  64. package/src/utils/normalizeMerkleBranch.ts +15 -0
  65. package/src/utils/update.ts +30 -0
  66. package/src/utils/utils.ts +95 -0
  67. package/src/utils/verifyMerkleBranch.ts +29 -0
  68. package/src/utils.ts +2 -0
  69. 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
+ }