Skip to content

Commit

Permalink
Validate SyncCommitteeSignatureSet
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion committed Jun 2, 2021
1 parent e6205fe commit 89dd4e3
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {allForks} from "@chainsafe/lodestar-types";
import {ISignatureSet} from "../../util";
import {allForks, altair} from "@chainsafe/lodestar-types";
import {computeEpochAtSlot, ISignatureSet} from "../../util";
import {CachedBeaconState} from "../util";
import {getProposerSlashingsSignatureSets} from "./proposerSlashings";
import {getAttesterSlashingsSignatureSets} from "./attesterSlashings";
import {getAttestationsSignatureSets} from "./indexedAttestation";
import {getProposerSignatureSet} from "./proposer";
import {getRandaoRevealSignatureSet} from "./randao";
import {getVoluntaryExitsSignatureSets} from "./voluntaryExits";
import {getSyncCommitteeSignatureSet} from "../../altair/block/processSyncCommittee";

export * from "./attesterSlashings";
export * from "./indexedAttestation";
Expand Down Expand Up @@ -34,11 +35,25 @@ export function getAllBlockSignatureSetsExceptProposer(
state: CachedBeaconState<allForks.BeaconState>,
signedBlock: allForks.SignedBeaconBlock
): ISignatureSet[] {
return [
const signatureSets = [
getRandaoRevealSignatureSet(state, signedBlock.message),
...getProposerSlashingsSignatureSets(state, signedBlock),
...getAttesterSlashingsSignatureSets(state, signedBlock),
...getAttestationsSignatureSets(state, signedBlock),
...getVoluntaryExitsSignatureSets(state, signedBlock),
];

// Only after altair fork, validate tSyncCommitteeSignature
if (computeEpochAtSlot(state.config, signedBlock.message.slot) > state.config.params.ALTAIR_FORK_EPOCH) {
const syncCommitteeSignatureSet = getSyncCommitteeSignatureSet(
state as CachedBeaconState<altair.BeaconState>,
(signedBlock as altair.SignedBeaconBlock).message.body.syncAggregate
);
// There may be no participants in this syncCommitteeSignature, so it must not be validated
if (syncCommitteeSignatureSet) {
signatureSets.push(syncCommitteeSignatureSet);
}
}

return signatureSets;
}
2 changes: 1 addition & 1 deletion packages/beacon-state-transition/src/altair/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ export function processBlock(
processRandao(state as CachedBeaconState<allForks.BeaconState>, block, verifySignatures);
processEth1Data(state as CachedBeaconState<allForks.BeaconState>, block.body);
processOperations(state, block.body, verifySignatures);
processSyncCommittee(state, block.body.syncAggregate, verifySignatures);
processSyncCommittee(state, block, verifySignatures);
}
Original file line number Diff line number Diff line change
@@ -1,63 +1,88 @@
import {verifyAggregate} from "@chainsafe/bls";
import {altair} from "@chainsafe/lodestar-types";
import {assert} from "@chainsafe/lodestar-utils";

import {
computeEpochAtSlot,
computeSigningRoot,
getBlockRootAtSlot,
getDomain,
increaseBalance,
ISignatureSet,
SignatureSetType,
verifySignatureSet,
zipIndexesInBitList,
} from "../../util";
import {CachedBeaconState} from "../../allForks/util";
import {BitList, isTreeBacked, TreeBacked} from "@chainsafe/ssz";

export function processSyncCommittee(
state: CachedBeaconState<altair.BeaconState>,
aggregate: altair.SyncAggregate,
block: altair.BeaconBlock,
verifySignatures = true
): void {
const {config, epochCtx} = state;
const {syncParticipantReward, syncProposerReward} = epochCtx;
const previousSlot = Math.max(state.slot, 1) - 1;
const committeeIndices = state.currSyncCommitteeIndexes;
const {syncParticipantReward, syncProposerReward} = state.epochCtx;
const syncAggregate = block.body.syncAggregate;
// the only time aggregate is not a TreeBacked is when producing a new block
const participantIndices = isTreeBacked(aggregate)
? zipIndexesInBitList(
committeeIndices,
aggregate.syncCommitteeBits as TreeBacked<BitList>,
config.types.altair.SyncCommitteeBits
)
: committeeIndices.filter((index) => !!aggregate.syncCommitteeBits[index]);
const participantPubkeys = participantIndices.map((validatorIndex) => state.validators[validatorIndex].pubkey);
const domain = getDomain(
config,
state,
config.params.DOMAIN_SYNC_COMMITTEE,
computeEpochAtSlot(config, previousSlot)
);
const signingRoot = computeSigningRoot(
config,
config.types.Root,
getBlockRootAtSlot(config, state, previousSlot),
domain
);
const participantIndices = getParticipantIndices(state, syncAggregate);

// different from the spec but not sure how to get through signature verification for default/empty SyncAggregate in the spec test
if (verifySignatures && participantIndices.length > 0) {
assert.true(
verifyAggregate(
participantPubkeys.map((pubkey) => pubkey.valueOf() as Uint8Array),
signingRoot,
aggregate.syncCommitteeSignature.valueOf() as Uint8Array
),
"Sync committee signature invalid"
);
if (verifySignatures) {
const signatureSet = getSyncCommitteeSignatureSet(state, syncAggregate);
// When there's no participation we consider the signature valid and just ignore i
if (signatureSet !== null && !verifySignatureSet(signatureSet)) {
throw Error("Sync committee signature invalid");
}
}

const proposerIndex = epochCtx.getBeaconProposer(state.slot);
const proposerIndex = state.epochCtx.getBeaconProposer(state.slot);
for (const participantIndex of participantIndices) {
increaseBalance(state, participantIndex, syncParticipantReward);
}
increaseBalance(state, proposerIndex, syncProposerReward * BigInt(participantIndices.length));
}

export function getSyncCommitteeSignatureSet(
state: CachedBeaconState<altair.BeaconState>,
syncAggregate: altair.SyncAggregate,
/** Optional parameter to prevent computing it twice */
participantIndices?: number[]
): ISignatureSet | null {
const {config, epochCtx} = state;

const previousSlot = Math.max(state.slot, 1) - 1;
const rootSigned = getBlockRootAtSlot(config, state, previousSlot);

// the only time aggregate is not a TreeBacked is when producing a new block
if (!participantIndices) {
participantIndices = getParticipantIndices(state, syncAggregate);
}

// When there's no participation we consider the signature valid and just ignore it
if (participantIndices.length === 0) {
return null;
}

const epochSig = computeEpochAtSlot(config, previousSlot);
const domain = getDomain(config, state, config.params.DOMAIN_SYNC_COMMITTEE, epochSig);

return {
type: SignatureSetType.aggregate,
pubkeys: participantIndices.map((i) => epochCtx.index2pubkey[i]),
signingRoot: computeSigningRoot(config, config.types.Root, rootSigned, domain),
signature: syncAggregate.syncCommitteeSignature.valueOf() as Uint8Array,
};
}

/** Common logic for processSyncCommittee() and getSyncCommitteeSignatureSet() */
function getParticipantIndices(
state: CachedBeaconState<altair.BeaconState>,
syncAggregate: altair.SyncAggregate
): number[] {
const committeeIndices = state.currSyncCommitteeIndexes;

return isTreeBacked(syncAggregate)
? zipIndexesInBitList(
committeeIndices,
syncAggregate.syncCommitteeBits as TreeBacked<BitList>,
state.config.types.altair.SyncCommitteeBits
)
: committeeIndices.filter((index) => !!syncAggregate.syncCommitteeBits[index]);
}

0 comments on commit 89dd4e3

Please sign in to comment.