mergedhumanPR #152Transport
Redeem_Grimm: authenticate failure-detector heartbeats against a forgery attack
This is a direct follow-up to the failure-detector layer merged in #46. In the review on merge, @Skyrider3 flagged two gaps, and both are worth fixing:
Author
@Redeem-Grimm-Satoshi
github profile →- Status
- Merged
- Merged on
- Jul 10
- Branch
- hackathon/redeem-grimm-signed-heartbeats
Description
The pitch.
## Problem This is a direct follow-up to the failure-detector layer merged in #46. In the review on merge, @Skyrider3 flagged two gaps, and both are worth fixing: 1. Heartbeat identity was spoofable by design. The observer read the peer id out of the heartbeat payload (`FDHB|<id>`) rather than authenticating it. A Byzantine agent could broadcast `FDHB|target-0` while the real `target-0` was down and keep it looking alive indefinitely. The base scenario ran with zero Byzantine agents, so this never fired, but the whole point of the framework is to prove a protocol holds up under attack, and the shipped accuracy validator only discriminated a naive implementation (the fixed-timeout foil), not an attacker. 2. The accuracy validator hardcoded `_MAX_PLAUSIBLE_GAP = 22.0`, which is the scenario's `hb_max` (20) plus a margin. Re-running the scenario with a different heartbeat bound would silently invalidate the threshold. This PR closes both, and it does so with the same discriminator discipline the original used: a validator that fails against the weak implementation and passes against the hardened one. ## Persona The keep-alive attack is the failure a real detector has to survive, so authentication belongs in the heartbeat path, not in an appendix. The design question is where to put it. A failure detector should stay a pure liveness oracle over observations it can trust, so I put verification in the observing agent and left the `FailureDetector` contract untouched. That mirrors how the rest of the stack works: the identity layer is already right there, and the gossip and HotStuff scenarios already sign their writes with it, so a heartbeat is just one more signed message. I used the existing `did_key` identity plugin rather than inventing anything. ## What is in the diff | Path | Purpose | |---|---| | `.../scenarios_builtin/failure_detection.py` | Heartbeats are now signed and timestamped: `FDHB|<id>|<ts>|<sig-hex>`. New public helpers `heartbeat_payload`, `claimed_peer`, and `verify_heartbeat` form the wire-and-verify surface. `verify_heartbeat` rejects anything that is not well formed, not signed by the claimed peer's key, from the future, or not strictly newer than the last accepted beat from that peer. A new `ByzantineForgerAgent` mounts the attack (fabricated beats signed with its own key, plus byte-exact replays of captured genuine beats). The observer runs in verifying mode by default and a trusting mode as the foil. The observer also emits one `fd:config` marker so the validators can read the scenario's heartbeat bound from the trace. | | `scenarios/failure_detection_forgery.yaml` | The forgery scenario: observer, target, always-live peer, and one forger impersonating the target during its outage. Task type `failure_detection_forgery`, four agents, zero message drop, deterministic. | | `packages/nest-core/nest_core/validators.py` | New `validate_failure_detection_no_forged_liveness`: for every outage segment during which forged beats arrived (a received `FDHB|` whose claimed id differs from the transport sender), the victim must still be suspected at the last in-segment status update. Plus `_fd_max_plausible_gap`, which derives the accuracy bound from the `fd:config` marker (`hb_max + margin`) with the old constant kept only as a fallback for pre-marker traces. | | `packages/nest-core/nest_core/scenarios.py` | Registers the `failure_detection_forgery` task type (same factory, different roles). | | `packages/nest-plugins-reference/tests/test_failure_detection.py` | Twelve new tests: signed-heartbeat unit tests (authentic accepted; fabricated, replayed, future-dated, and malformed rejected), the three-seed forgery integration pass, the trusting-vs-verifying discriminator, the config-derived accuracy bound, and forgery-scenario determinism. | | `packages/nest-core/tests/test_validators.py` | Adds `failure_detection_forgery` to the registry-completeness assertion. | The base `failure_detection` …
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/152/head:pr-152
git checkout pr-152