Skip to content

feat: punish confirmed light-client attacks at double-sign severity - #71

Open
giunatale wants to merge 1 commit into
giunatale/feat/offline-detectionfrom
giunatale/feat/lightclient-attack-punishment
Open

feat: punish confirmed light-client attacks at double-sign severity#71
giunatale wants to merge 1 commit into
giunatale/feat/offline-detectionfrom
giunatale/feat/lightclient-attack-punishment

Conversation

@giunatale

@giunatale giunatale commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

A confirmed light-client attack on a consumer was previously detection-only:
the byzantine validators were extracted, logged, and returned — no slashing,
jailing, or tombstoning. That left the cheapest way to attack a consumer chain
unpunished, while the equivalent offence via double-voting was tombstoned.

What

HandleConsumerMisbehaviour now punishes:

  • The misbehaviour is verified as before (chain id and client id match the
    consumer, headers at the same height within the trusting period, genuinely
    conflicting, each valid against its trusted consensus state).
  • Every validator that signed both conflicting headers is punished through the
    same primitive as consumer double-voting — slash, jail, and tombstone at the
    double-sign infraction parameters. The punishment code is now factored and
    shared (punishEquivocation); the two evidence types differ only in
    verification. Already-tombstoned validators are a no-op, so repeated
    submissions stay idempotent.
  • When no byzantine validator can be punished, the provider stops the consumer
    and schedules it for removal rather than no-opping on a proven-compromised
    chain. This covers an amnesia attack, which has no attributable signer by
    construction, and also the case where every extracted signer turns out to be
    unpunishable (all unbonded). This is terminal: nothing moves a consumer out
    of STOPPED, so there is no veto or cancel path — deliberate, since the chain
    has been proven byzantine. Escalation only applies to a LAUNCHED consumer; one
    already stopping is left alone.

Testing

Unit: identifiable byzantine signers are slashed, jailed, and tombstoned at the
double-sign fraction; repeated submission is idempotent; an unidentifiable
attack stops the consumer and schedules removal; a non-launched consumer is not
escalated. Build, lint, and the module suite are green.

@giunatale

Copy link
Copy Markdown
Contributor Author

Branched from giunatale/feat/offline-detection (#63).
Opened against that branch so only this PR's commit shows; will retarget to main after #63 lands and this rebases.

@giunatale
giunatale force-pushed the giunatale/feat/lightclient-attack-punishment branch from 8138d89 to 6ea06c8 Compare August 26, 2026 12:38

@julienrbrt julienrbrt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This

} else {
}

if len(punished) == 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty harsh isn't it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folded into the reply on the other thread: under option A the terminal escalation is dropped outright, under option B it disappears together with the automatic punishment path. Either way this resolves with the choice there.

// document this as the intended posture ("Light Client Misbehavior:
// detection and logging"). Validator punishment for equivocation goes through
// MsgSubmitConsumerDoubleVoting / HandleConsumerDoubleVoting, which slashes
// against a cryptographically self-contained DuplicateVoteEvidence. Light-

@julienrbrt julienrbrt Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Light-
// client misbehaviour evidence is more involved to verify end-to-end against
// a buggy or adversarial consumer chain, and double-vote evidence already
// covers the same validator-equivocation case; punishing twice along two
// different paths is unnecessary.

This concern is still valid, even tho the attack is more involved:

  • Create consumer chain
  • Provide binary to validators, and pay them (they have no incentive to refuse validating your chain)
  • Trigger fake light client attack from your consumer chain binary
  • Tombstone the whole AtomOne validator set

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attack verifies, and it is worth being precise about why: nothing in it is forged. Every signature the punisher would act on is a real signature by a real provider validator's consumer key. The malicious binary orchestrates a fork at the application layer, each node signs each header once believing it legitimate, so the per-node double-sign protection (priv_validator's last-sign state) never triggers. The provider cannot distinguish that evidence from a genuine coalition attack, because the distinction does not exist in the data.

One scoping fact before the options: the same vector already exists on the base branch through MsgSubmitConsumerDoubleVoting, which slashes, jails, and tombstones per validator. A binary that forks the chain makes every honest validator produce valid DuplicateVoteEvidence, and the owner can submit them one by one. So the exposure is a property of punishing consumer-key equivocation under mandatory validation (VAAS has no opt-in), not something this PR introduces. What this PR uniquely adds is coalition-level attribution, and the coalition signal is the more victim-biased one: a same-height double-sign by more than two thirds of the set is not plausible as independent operator error; the dominant explanation is exactly "everyone ran the same binary".

Two concrete resolutions; I would like your pick.

A. Keep automatic punishment, drop the escalation. The byzantine set keeps being slashed, jailed and tombstoned at DoubleSign severity, but an unpunishable attack (amnesia, or all signers unbonded) logs and surfaces instead of moving the consumer to STOPPED terminally. This answers your other comment directly and keeps a confirmed coalition punishable, at the cost of the inconsistency you point at: it still tombstones automatically a set that is most likely victims.

B. Detection plus a governance decision. HandleConsumerMisbehaviour verifies the evidence and stores a confirmed-attack record (byzantine set, height, evidence hash) instead of punishing; a new governance-gated message executes the recorded punishment through the same punishEquivocation primitive, and governance can already stop the chain with MsgRemoveConsumer. Automatic response stays reserved for the per-validator double-vote path, where a lone double-sign is at least plausibly the validator's own fault; coalition punishment, where victims dominate, moves behind human deliberation, the only layer that can weigh what the binary actually was. Note the timing constraint: the record is only punishable while the signers still have bonded stake, so the decision has to fit inside the unbonding period.

I lean B: this PR overturned a deliberate detection-only decision, and your attack is the argument that decision rested on, holding up; B restores it without leaving a confirmed attack unactionable. Either way, the residual exposure gets a security-model entry in #67: consumer-submitted equivocation evidence can tombstone honest provider validators that ran a malicious binary, via the double-vote path; the structural mitigations are model-level (binary vetting on binary_hash, or opt-in validation, which VAAS deliberately does not have).

HandleConsumerMisbehaviour was detection-only: it verified a light-client
attack and logged the byzantine set but applied no punishment. It now slashes,
jails, and tombstones the validators that signed both conflicting headers at
the DoubleSign infraction severity, identical to vote-level double signing.

The slash/jail/tombstone path is factored into a shared punishEquivocation
primitive that HandleConsumerDoubleVoting also uses, so the two equivocation
paths differ only in how the evidence is verified, not in how the equivocation
is punished.

When an attack is confirmed but no validator can be punished (an amnesia attack
has no identifiable signers; other conflicts may leave only unbonded signers),
the consumer is stopped and scheduled for removal via
StopAndPrepareForConsumerRemoval instead of a silent no-op. The escalation only
fires from the launched phase, so re-submitting the same evidence does not
schedule removal twice.
@giunatale
giunatale force-pushed the giunatale/feat/lightclient-attack-punishment branch from 6ea06c8 to fb8ced5 Compare August 28, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants