Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid using BigInt for slashing classes #6116

Closed
wants to merge 9 commits into from
4 changes: 2 additions & 2 deletions packages/beacon-node/src/network/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,15 +343,15 @@ export class Network implements INetwork {
}

async publishProposerSlashing(proposerSlashing: phase0.ProposerSlashing): Promise<number> {
const fork = this.config.getForkName(Number(proposerSlashing.signedHeader1.message.slot as bigint));
const fork = this.config.getForkNameBytes8(proposerSlashing.signedHeader1.message.slot);
return this.publishGossip<GossipType.proposer_slashing>(
{type: GossipType.proposer_slashing, fork},
proposerSlashing
);
}

async publishAttesterSlashing(attesterSlashing: phase0.AttesterSlashing): Promise<number> {
const fork = this.config.getForkName(Number(attesterSlashing.attestation1.data.slot as bigint));
const fork = this.config.getForkNameBytes8(attesterSlashing.attestation1.data.slot);
return this.publishGossip<GossipType.attester_slashing>(
{type: GossipType.attester_slashing, fork},
attesterSlashing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {describe, it, beforeEach, afterEach, vi} from "vitest";
import {phase0, ssz} from "@lodestar/types";
import {intToBytes} from "@lodestar/utils";
import {generateCachedState} from "../../../utils/state.js";
import {validateGossipAttesterSlashing} from "../../../../src/chain/validation/attesterSlashing.js";
import {AttesterSlashingErrorCode} from "../../../../src/chain/errors/attesterSlashingError.js";
Expand Down Expand Up @@ -44,15 +45,15 @@ describe("GossipMessageValidator", () => {
});

it("should return valid attester slashing", async () => {
const attestationData = ssz.phase0.AttestationDataBigint.defaultValue();
const attestationData = ssz.phase0.AttestationDataBytes8.defaultValue();
const attesterSlashing: phase0.AttesterSlashing = {
attestation1: {
data: attestationData,
signature: Buffer.alloc(96, 0),
attestingIndices: [0],
},
attestation2: {
data: {...attestationData, slot: BigInt(1)}, // Make it different so it's slashable
data: {...attestationData, slot: intToBytes(1, 8)}, // Make it different so it's slashable
signature: Buffer.alloc(96, 0),
attestingIndices: [0],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {describe, it, beforeEach, afterEach, vi} from "vitest";
import {phase0, ssz} from "@lodestar/types";
import {intToBytes} from "@lodestar/utils";
import {generateCachedState} from "../../../utils/state.js";
import {ProposerSlashingErrorCode} from "../../../../src/chain/errors/proposerSlashingError.js";
import {validateGossipProposerSlashing} from "../../../../src/chain/validation/proposerSlashing.js";
Expand Down Expand Up @@ -36,8 +37,8 @@ describe("validate proposer slashing", () => {
it("should return invalid proposer slashing - invalid", async () => {
const proposerSlashing = ssz.phase0.ProposerSlashing.defaultValue();
// Make it invalid
proposerSlashing.signedHeader1.message.slot = BigInt(1);
proposerSlashing.signedHeader2.message.slot = BigInt(0);
proposerSlashing.signedHeader1.message.slot = intToBytes(1, 8);
proposerSlashing.signedHeader2.message.slot = intToBytes(0, 8);

await expectRejectedWithLodestarError(
validateGossipProposerSlashing(chainStub, proposerSlashing),
Expand All @@ -46,8 +47,8 @@ describe("validate proposer slashing", () => {
});

it("should return valid proposer slashing", async () => {
const signedHeader1 = ssz.phase0.SignedBeaconBlockHeaderBigint.defaultValue();
const signedHeader2 = ssz.phase0.SignedBeaconBlockHeaderBigint.defaultValue();
const signedHeader1 = ssz.phase0.SignedBeaconBlockHeaderBytes8.defaultValue();
const signedHeader2 = ssz.phase0.SignedBeaconBlockHeaderBytes8.defaultValue();
// Make it different, so slashable
signedHeader2.message.stateRoot = Buffer.alloc(32, 1);

Expand Down
1 change: 1 addition & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"dependencies": {
"@chainsafe/ssz": "^0.14.0",
"@lodestar/params": "^1.12.0",
"@lodestar/utils": "^1.12.0",
"@lodestar/types": "^1.12.0"
}
}
7 changes: 6 additions & 1 deletion packages/config/src/forkConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
isForkExecution,
isForkBlobs,
} from "@lodestar/params";
import {Slot, allForks, Version, ssz} from "@lodestar/types";
import {Slot, allForks, Version, ssz, Bytes8} from "@lodestar/types";
import {bytesToIntOrMaxInt} from "@lodestar/utils";
import {ChainConfig} from "../chainConfig/index.js";
import {ForkConfig, ForkInfo} from "./types.js";

Expand Down Expand Up @@ -81,6 +82,10 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
getForkName(slot: Slot): ForkName {
return this.getForkInfo(slot).name;
},
getForkNameBytes8(slotBufferLE: Bytes8): ForkName {
const slot = bytesToIntOrMaxInt(slotBufferLE);
return this.getForkName(slot);
},
getForkSeq(slot: Slot): ForkSeq {
return this.getForkInfo(slot).seq;
},
Expand Down
4 changes: 3 additions & 1 deletion packages/config/src/forkConfig/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ForkName, ForkSeq} from "@lodestar/params";
import {allForks, Epoch, Slot, Version} from "@lodestar/types";
import {allForks, Bytes8, Epoch, Slot, Version} from "@lodestar/types";

export type ForkInfo = {
name: ForkName;
Expand All @@ -24,6 +24,8 @@ export type ForkConfig = {

/** Get the hard-fork name at a given slot */
getForkName(slot: Slot): ForkName;
/** Get the hard-fork name at a given Uint8ArrayLE slot */
getForkNameBytes8(slot: Bytes8): ForkName;
/** Get the hard-fork sequence number at a given slot */
getForkSeq(slot: Slot): ForkSeq;
/** Get the hard-fork version at a given slot */
Expand Down
36 changes: 18 additions & 18 deletions packages/flare/src/cmds/selfSlashAttester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {phase0, ssz} from "@lodestar/types";
import {config as chainConfig} from "@lodestar/config/default";
import {createBeaconConfig, BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_ATTESTER, MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
import {toHexString} from "@lodestar/utils";
import {intToBytes, toHexString} from "@lodestar/utils";
import {computeSigningRoot} from "@lodestar/state-transition";
import {CliCommand} from "../util/command.js";
import {deriveSecretKeys, SecretKeysArgs, secretKeysOptions} from "../util/deriveSecretKeys.js";
Expand Down Expand Up @@ -107,31 +107,31 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise<voi

// Trigers a double vote, same target epoch different data (beaconBlockRoot)
// TODO: Allow to create double-votes
const data1: phase0.AttestationDataBigint = {
slot,
index: BigInt(0),
const data1: phase0.AttestationDataBytes8 = {
slot: intToBytes(slot, 8),
index: intToBytes(0, 8),
beaconBlockRoot: rootA,
source: {epoch: BigInt(0), root: rootA},
target: {epoch: BigInt(0), root: rootB},
source: {epoch: intToBytes(0, 8), root: rootA},
target: {epoch: intToBytes(0, 8), root: rootB},
};
const data2: phase0.AttestationDataBigint = {
slot,
index: BigInt(0),
const data2: phase0.AttestationDataBytes8 = {
slot: intToBytes(slot, 8),
index: intToBytes(0, 8),
beaconBlockRoot: rootB,
source: {epoch: BigInt(0), root: rootA},
target: {epoch: BigInt(0), root: rootB},
source: {epoch: intToBytes(0, 8), root: rootA},
target: {epoch: intToBytes(0, 8), root: rootB},
};

const attesterSlashing: phase0.AttesterSlashing = {
attestation1: {
attestingIndices,
data: data1,
signature: signAttestationDataBigint(config, sks, data1),
signature: signAttestationDataBytes8(config, sks, data1),
},
attestation2: {
attestingIndices,
data: data2,
signature: signAttestationDataBigint(config, sks, data2),
signature: signAttestationDataBytes8(config, sks, data2),
},
};

Expand All @@ -143,14 +143,14 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise<voi
}
}

function signAttestationDataBigint(
function signAttestationDataBytes8(
config: BeaconConfig,
sks: SecretKey[],
data: phase0.AttestationDataBigint
data: phase0.AttestationDataBytes8
): Uint8Array {
const slot = Number(data.slot as bigint);
const proposerDomain = config.getDomain(slot, DOMAIN_BEACON_ATTESTER);
const signingRoot = computeSigningRoot(ssz.phase0.AttestationDataBigint, data, proposerDomain);
const fork = config.getForkNameBytes8(data.slot);
const proposerDomain = config.getDomainAtFork(fork, DOMAIN_BEACON_ATTESTER);
const signingRoot = computeSigningRoot(ssz.phase0.AttestationDataBytes8, data, proposerDomain);

const sigs = sks.map((sk) => sk.sign(signingRoot));
return bls.Signature.aggregate(sigs).toBytes();
Expand Down
22 changes: 11 additions & 11 deletions packages/flare/src/cmds/selfSlashProposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {phase0, ssz} from "@lodestar/types";
import {config as chainConfig} from "@lodestar/config/default";
import {createBeaconConfig, BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
import {toHexString} from "@lodestar/utils";
import {intToBytes, toHexString} from "@lodestar/utils";
import {computeSigningRoot} from "@lodestar/state-transition";
import {CliCommand} from "../util/command.js";
import {deriveSecretKeys, SecretKeysArgs, secretKeysOptions} from "../util/deriveSecretKeys.js";
Expand Down Expand Up @@ -99,15 +99,15 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise<voi
console.log(`Warning: validator index ${index} is already slashed`);
}

const header1: phase0.BeaconBlockHeaderBigint = {
slot,
const header1: phase0.BeaconBlockHeaderBytes8 = {
slot: intToBytes(slot, 8),
proposerIndex: index,
parentRoot: rootA,
stateRoot: rootA,
bodyRoot: rootA,
};
const header2: phase0.BeaconBlockHeaderBigint = {
slot,
const header2: phase0.BeaconBlockHeaderBytes8 = {
slot: intToBytes(slot, 8),
proposerIndex: index,
parentRoot: rootB,
stateRoot: rootB,
Expand All @@ -117,11 +117,11 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise<voi
const proposerSlashing: phase0.ProposerSlashing = {
signedHeader1: {
message: header1,
signature: signHeaderBigint(config, sk, header1),
signature: signHeaderBytes8(config, sk, header1),
},
signedHeader2: {
message: header2,
signature: signHeaderBigint(config, sk, header2),
signature: signHeaderBytes8(config, sk, header2),
},
};

Expand All @@ -136,9 +136,9 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise<voi
}
}

function signHeaderBigint(config: BeaconConfig, sk: SecretKey, header: phase0.BeaconBlockHeaderBigint): Uint8Array {
const slot = Number(header.slot as bigint);
const proposerDomain = config.getDomain(slot, DOMAIN_BEACON_PROPOSER);
const signingRoot = computeSigningRoot(ssz.phase0.BeaconBlockHeaderBigint, header, proposerDomain);
function signHeaderBytes8(config: BeaconConfig, sk: SecretKey, header: phase0.BeaconBlockHeaderBytes8): Uint8Array {
const fork = config.getForkNameBytes8(header.slot);
const proposerDomain = config.getDomainAtFork(fork, DOMAIN_BEACON_PROPOSER);
const signingRoot = computeSigningRoot(ssz.phase0.BeaconBlockHeaderBytes8, header, proposerDomain);
return sk.sign(signingRoot).toBytes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
import {phase0} from "@lodestar/types";
import {CachedBeaconStateAllForks} from "../types.js";
import {verifySignatureSet} from "../util/index.js";
import {getIndexedAttestationBigintSignatureSet, getIndexedAttestationSignatureSet} from "../signatureSets/index.js";
import {getIndexedAttestationBytes8SignatureSet, getIndexedAttestationSignatureSet} from "../signatureSets/index.js";

/**
* Check if `indexedAttestation` has sorted and unique indices and a valid aggregate signature.
Expand All @@ -23,17 +23,17 @@ export function isValidIndexedAttestation(
}
}

export function isValidIndexedAttestationBigint(
export function isValidIndexedAttestationBytes8(
state: CachedBeaconStateAllForks,
indexedAttestation: phase0.IndexedAttestationBigint,
indexedAttestation: phase0.IndexedAttestationBytes8,
verifySignature: boolean
): boolean {
if (!isValidIndexedAttestationIndices(state, indexedAttestation.attestingIndices)) {
return false;
}

if (verifySignature) {
return verifySignatureSet(getIndexedAttestationBigintSignatureSet(state, indexedAttestation));
return verifySignatureSet(getIndexedAttestationBytes8SignatureSet(state, indexedAttestation));
} else {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ForkSeq} from "@lodestar/params";
import {isSlashableValidator, isSlashableAttestationData, getAttesterSlashableIndices} from "../util/index.js";
import {CachedBeaconStateAllForks} from "../types.js";
import {slashValidator} from "./slashValidator.js";
import {isValidIndexedAttestationBigint} from "./isValidIndexedAttestation.js";
import {isValidIndexedAttestationBytes8} from "./isValidIndexedAttestation.js";

/**
* Process an AttesterSlashing operation. Initiates the exit of a validator, decreases the balance of the slashed
Expand Down Expand Up @@ -53,7 +53,7 @@ export function assertValidAttesterSlashing(
// be higher than the clock and the slashing would still be valid. Same applies to attestation data index, which
// can be any arbitrary value. Must use bigint variants to hash correctly to all possible values
for (const [i, attestation] of [attestation1, attestation2].entries()) {
if (!isValidIndexedAttestationBigint(state, attestation, verifySignatures)) {
if (!isValidIndexedAttestationBytes8(state, attestation, verifySignatures)) {
throw new Error(`AttesterSlashing attestation${i} is invalid`);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {phase0, ssz} from "@lodestar/types";
import {ForkSeq} from "@lodestar/params";
import {bytesToInt} from "@lodestar/utils";
import {isSlashableValidator} from "../util/index.js";
import {verifySignatureSet} from "../util/signatureSets.js";
import {CachedBeaconStateAllForks} from "../types.js";
Expand Down Expand Up @@ -32,8 +33,10 @@ export function assertValidProposerSlashing(
const header2 = proposerSlashing.signedHeader2.message;

// verify header slots match
if (header1.slot !== header2.slot) {
throw new Error(`ProposerSlashing slots do not match: slot1=${header1.slot} slot2=${header2.slot}`);
if (Buffer.compare(header1.slot, header2.slot) !== 0) {
throw new Error(
`ProposerSlashing slots do not match: slot1=${bytesToInt(header1.slot)} slot2=${bytesToInt(header2.slot)}`
);
}

// verify header proposer indices match
Expand All @@ -44,7 +47,7 @@ export function assertValidProposerSlashing(
}

// verify headers are different
if (ssz.phase0.BeaconBlockHeaderBigint.equals(header1, header2)) {
if (ssz.phase0.BeaconBlockHeaderBytes8.equals(header1, header2)) {
throw new Error("ProposerSlashing headers are equal");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {allForks, phase0, ssz} from "@lodestar/types";
import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params";
import {computeSigningRoot, computeStartSlotAtEpoch, ISignatureSet, SignatureSetType} from "../util/index.js";
import {computeSigningRoot, ISignatureSet, SignatureSetType} from "../util/index.js";
import {CachedBeaconStateAllForks} from "../types.js";

/** Get signature sets from all AttesterSlashing objects in a block */
Expand All @@ -19,22 +19,22 @@ export function getAttesterSlashingSignatureSets(
attesterSlashing: phase0.AttesterSlashing
): ISignatureSet[] {
return [attesterSlashing.attestation1, attesterSlashing.attestation2].map((attestation) =>
getIndexedAttestationBigintSignatureSet(state, attestation)
getIndexedAttestationBytes8SignatureSet(state, attestation)
);
}

export function getIndexedAttestationBigintSignatureSet(
export function getIndexedAttestationBytes8SignatureSet(
state: CachedBeaconStateAllForks,
indexedAttestation: phase0.IndexedAttestationBigint
indexedAttestation: phase0.IndexedAttestationBytes8
): ISignatureSet {
const {index2pubkey} = state.epochCtx;
const slot = computeStartSlotAtEpoch(Number(indexedAttestation.data.target.epoch as bigint));
const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_ATTESTER, slot);
const fork = state.config.getForkNameBytes8(indexedAttestation.data.slot);
const domain = state.config.getDomainAtFork(fork, DOMAIN_BEACON_ATTESTER);

return {
type: SignatureSetType.aggregate,
pubkeys: indexedAttestation.attestingIndices.map((i) => index2pubkey[i]),
signingRoot: computeSigningRoot(ssz.phase0.AttestationDataBigint, indexedAttestation.data, domain),
signingRoot: computeSigningRoot(ssz.phase0.AttestationDataBytes8, indexedAttestation.data, domain),
signature: indexedAttestation.signature,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ export function getProposerSlashingSignatureSets(
// In state transition, ProposerSlashing headers are only partially validated. Their slot could be higher than the
// clock and the slashing would still be valid. Must use bigint variants to hash correctly to all possible values
return [proposerSlashing.signedHeader1, proposerSlashing.signedHeader2].map((signedHeader): ISignatureSet => {
const domain = state.config.getDomain(
state.slot,
DOMAIN_BEACON_PROPOSER,
Number(signedHeader.message.slot as bigint)
);
const fork = state.config.getForkNameBytes8(signedHeader.message.slot);
const domain = state.config.getDomainAtFork(fork, DOMAIN_BEACON_PROPOSER);

return {
type: SignatureSetType.single,
pubkey,
signingRoot: computeSigningRoot(ssz.phase0.BeaconBlockHeaderBigint, signedHeader.message, domain),
signingRoot: computeSigningRoot(ssz.phase0.BeaconBlockHeaderBytes8, signedHeader.message, domain),
signature: signedHeader.signature,
};
});
Expand Down
Loading
Loading