-
-
Notifications
You must be signed in to change notification settings - Fork 311
/
validateLightClientUpdate.ts
150 lines (135 loc) Β· 5.01 KB
/
validateLightClientUpdate.ts
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
import {Root, ssz, allForks} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import bls from "@chainsafe/bls/switchable";
import type {PublicKey, Signature} from "@chainsafe/bls/types";
import {
FINALIZED_ROOT_INDEX,
FINALIZED_ROOT_DEPTH,
NEXT_SYNC_COMMITTEE_INDEX,
NEXT_SYNC_COMMITTEE_DEPTH,
MIN_SYNC_COMMITTEE_PARTICIPANTS,
DOMAIN_SYNC_COMMITTEE,
GENESIS_SLOT,
} from "@lodestar/params";
import {getParticipantPubkeys, sumBits} from "../utils/utils.js";
import {isValidMerkleBranch} from "../utils/index.js";
import {SyncCommitteeFast} from "../types.js";
import {
isFinalityUpdate,
isSyncCommitteeUpdate,
isZeroedHeader,
isZeroedSyncCommittee,
ZERO_HASH,
isValidLightClientHeader,
} from "./utils.js";
import {ILightClientStore} from "./store.js";
export function validateLightClientUpdate(
config: ChainForkConfig,
store: ILightClientStore,
update: allForks.LightClientUpdate,
syncCommittee: SyncCommitteeFast
): void {
// Verify sync committee has sufficient participants
if (sumBits(update.syncAggregate.syncCommitteeBits) < MIN_SYNC_COMMITTEE_PARTICIPANTS) {
throw Error("Sync committee has not sufficient participants");
}
if (!isValidLightClientHeader(config, update.attestedHeader)) {
throw Error("Attested Header is not Valid Light Client Header");
}
// Sanity check that slots are in correct order
if (update.signatureSlot <= update.attestedHeader.beacon.slot) {
throw Error(
`signature slot ${update.signatureSlot} must be after attested header slot ${update.attestedHeader.beacon.slot}`
);
}
if (update.attestedHeader.beacon.slot < update.finalizedHeader.beacon.slot) {
throw Error(
`attested header slot ${update.signatureSlot} must be after finalized header slot ${update.finalizedHeader.beacon.slot}`
);
}
// Verify that the `finality_branch`, if present, confirms `finalized_header`
// to match the finalized checkpoint root saved in the state of `attested_header`.
// Note that the genesis finalized checkpoint root is represented as a zero hash.
if (!isFinalityUpdate(update)) {
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
throw Error("finalizedHeader must be zero for non-finality update");
}
} else {
let finalizedRoot: Root;
if (update.finalizedHeader.beacon.slot === GENESIS_SLOT) {
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
throw Error("finalizedHeader must be zero for not finality update");
}
finalizedRoot = ZERO_HASH;
} else {
if (!isValidLightClientHeader(config, update.finalizedHeader)) {
throw Error("Finalized Header is not valid Light Client Header");
}
finalizedRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon);
}
if (
!isValidMerkleBranch(
finalizedRoot,
update.finalityBranch,
FINALIZED_ROOT_DEPTH,
FINALIZED_ROOT_INDEX,
update.attestedHeader.beacon.stateRoot
)
) {
throw Error("Invalid finality header merkle branch");
}
}
// Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the
// state of the `attested_header`
if (!isSyncCommitteeUpdate(update)) {
if (!isZeroedSyncCommittee(update.nextSyncCommittee)) {
throw Error("nextSyncCommittee must be zero for non sync committee update");
}
} else {
if (
!isValidMerkleBranch(
ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee),
update.nextSyncCommitteeBranch,
NEXT_SYNC_COMMITTEE_DEPTH,
NEXT_SYNC_COMMITTEE_INDEX,
update.attestedHeader.beacon.stateRoot
)
) {
throw Error("Invalid next sync committee merkle branch");
}
}
// Verify sync committee aggregate signature
const participantPubkeys = getParticipantPubkeys(syncCommittee.pubkeys, update.syncAggregate.syncCommitteeBits);
const signingRoot = ssz.phase0.SigningData.hashTreeRoot({
objectRoot: ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.attestedHeader.beacon),
domain: store.config.getDomain(update.signatureSlot, DOMAIN_SYNC_COMMITTEE),
});
if (!isValidBlsAggregate(participantPubkeys, signingRoot, update.syncAggregate.syncCommitteeSignature)) {
throw Error("Invalid aggregate signature");
}
}
/**
* Same as BLS.verifyAggregate but with detailed error messages
*/
function isValidBlsAggregate(publicKeys: PublicKey[], message: Uint8Array, signature: Uint8Array): boolean {
let aggPubkey: PublicKey;
try {
aggPubkey = bls.PublicKey.aggregate(publicKeys);
} catch (e) {
(e as Error).message = `Error aggregating pubkeys: ${(e as Error).message}`;
throw e;
}
let sig: Signature;
try {
sig = bls.Signature.fromBytes(signature, undefined, true);
} catch (e) {
(e as Error).message = `Error deserializing signature: ${(e as Error).message}`;
throw e;
}
try {
return sig.verify(aggPubkey, message);
} catch (e) {
(e as Error).message = `Error verifying signature: ${(e as Error).message}`;
throw e;
}
}