Skip to content

fix(ssv_types): make ValidatorIndex SSZ encoding architecture-independent #1132

Description

@shane-moore

Goal

ssv_types::ValidatorIndex SSZ-encodes to exactly 8 little-endian bytes on every compilation target, matching the uint64 that the Ethereum consensus spec, ssv-spec, and go-ssv define for validator indices.

Context / motivation

ValidatorIndex is pub struct ValidatorIndex(pub usize) with #[ssz(struct_behaviour = "transparent")] (anchor/common/ssv_types/src/cluster.rs:77-78), so SSZ encoding forwards to usize. In ethereum_ssz 0.10.1 (the version pinned by Cargo.lock), the usize Encode/Decode impls are gated on #[cfg(target_pointer_width)] (src/encode/impls.rs:40-44, src/decode/impls.rs:48-52): 8 bytes on 64-bit targets, 4 bytes on 32-bit targets, and decode rejects any other width.

Three protocol-visible containers embed the field, all of which are SSZ-encoded into SSVMessage.data, RSA-signed, and gossiped:

  • PartialSignatureMessage (anchor/common/ssv_types/src/partial_sig.rs:129-133)
  • ValidatorDuty (anchor/common/ssv_types/src/consensus.rs:447-451)
  • AssignedAggregator (anchor/common/ssv_types/src/consensus.rs:495-497)

A 32-bit build would therefore emit 4-byte index fields (a one-entry PartialSignatureMessages payload shrinks from 164 to 160 bytes) and fail to decode the 8-byte fields produced by go-ssv, which uses phase0.ValidatorIndex (uint64) in the same message.

This is a latent portability defect, not a live incompatibility: all release binaries (x86_64/aarch64 Linux, aarch64 Darwin) are 64-bit and byte-compatible with go-ssv today. Exposure is limited to source builds on 32-bit targets. Tree-hash roots are already width-independent (tree_hash 0.12.1 widens usize to u64), so only the SSZ serialization is affected. A wire type's byte layout should not depend on the pointer width of the machine that compiled it.

Suggested approach

Change the inner type to u64, matching the OperatorId(pub u64) precedent in the same crate. This is byte-identical on all 64-bit targets and makes the encoding width-independent by construction.

The change is net-simplifying because Lighthouse already hands Anchor u64 indices; the current narrowing casts at that boundary get deleted rather than guarded:

  • anchor/eth/src/index_sync.rs:140
  • anchor/validator_store/src/metadata_service.rs:334, :363, :728, :774
  • anchor/validator_store/src/lib.rs:2343

FromSql/ToSql (cluster.rs:80-94) keep their checked i64 conversions, so the on-disk representation is unchanged and no migration is needed. ssv_types has no serde surface, so no JSON/API impact.

An alternative is keeping pub usize and hand-writing fixed 8-byte Encode/Decode (as PartialSignatureKind does), but nothing in the codebase uses the inner usize as a collection index, so the extra code buys nothing and leaves a silent-truncation footgun on 32-bit constructors.

Acceptance criteria

  • ValidatorIndex SSZ-encodes to exactly 8 little-endian bytes independent of target pointer width, and its tree-hash root equals that of an SSZ uint64.
  • Zero byte changes on 64-bit builds: a one-entry PartialSignatureMessages payload remains 164 bytes.
  • All conversions between the index and target-sized integers are checked; no silent truncation.
  • Database rows containing validator_index read back identically (i64 column unchanged, conversions still checked).

Tests

  • Golden encoded-byte assertions for ValidatorIndex(0), ValidatorIndex(1), and ValidatorIndex(u64::MAX).
  • A one-entry PartialSignatureMessages encode round-trip asserting the 164-byte layout.
  • Width-independence follows by construction from delegating to u64 (whose impls are not cfg-gated); a 32-bit cargo check in CI is optional reinforcement.

Notes

Issues are directionally correct, not prescriptive; verify symbols at PR time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions