Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
in reviewhumanPR #71Identity

chainaim-agentID-engineer: Ed25519 identity with KERI pre-rotation and hijack-resistant key rotation

Problem #05 — identity / Ed25519 key rotation. Adds a KERI-style pre-rotation identity plugin at ("identity","ed25519_prerotation"), building on the merged reactive-rotation plugin (PR #25) to defend a third attack class — rotation hijack…

Author

chainaim-bala avatar

@chainaim-bala

github profile →
Status
In review
Opened on
Jul 5
Branch
hackathon/chainaim-identity-ed25519-prerotation-keri-hijack-defense

Description

The pitch.

# [Hackathon] chainaim-agentID-engineer: Ed25519 identity with KERI pre-rotation and hijack-resistant key rotation

## Short summary

Problem #05 — identity / Ed25519 key rotation. Adds a KERI-style **pre-rotation** identity plugin at `("identity","ed25519_prerotation")`, building on the merged reactive-rotation plugin (PR #25) to defend a third attack class — rotation hijack — in addition to post-rotation forgery and backdating, with a recovery path. It is additive: `ed25519_rotating` and `did_key` are unchanged, and the merged `identity_rotation` validators run as-is on these traces.

**Persona — chainaim-agentID-engineer.** The design is driven by a live-key-compromise risk model rather than a happy-path feature: the plugin assumes an attacker can exfiltrate the current signing key, and every adversarial test is mapped to a production incident — post-rotation forgery to a stolen retired key (old backup, ex-employee), backdating to an antedated contract or tampered audit timestamp, rotation hijack to a leaked CI secret, and recovery to the incident-response path of rotating to a pre-committed cold key that never touched the compromised host.

## What it does

At inception the plugin derives signing key `K0` and pre-derives cold key `K1`, publishing `digest(K1.pub)` as a commitment. `rotate_key(new_seed)` reveals the pre-committed `K1` and commits `digest(K2)`. `verify_continuity` accepts a rotation only when the revealed key hashes to the prior commitment, so a party holding only the current private key cannot install a successor of its own choosing. `verify` evaluates a signature against a verifier-supplied as-of tick (not `sig.signed_at`), which is what rejects forgery and backdating. `forge_rotation` and `sign_with` are adversarial-only hooks used by the scenario; they mutate no state.

**Visual explanation:** *https://chainaim-keri-prerotation.vercel.app/*

## Run and check

```
uv sync
uv run pytest packages/nest-plugins-reference/tests/test_ed25519_prerotation.py -v            # 28
uv run pytest packages/nest-plugins-reference/tests/test_ed25519_prerotation_properties.py -v  #  8
uv run pytest packages/nest-core/tests/test_identity_prerotation.py -v                         # 23
uv run nest run scenarios/identity_prerotation.yaml --seed 42
```

Full keyword-scoped suite (new work plus the merged identity tests it coexists with):

```
uv run pytest -k "ed25519 or identity_rotation or prerotation" -v
```

## Registration / key surface

|Surface|Where|
|-|-|
|`("identity","ed25519_prerotation")`|`plugins.py` `_BUILTINS` **and** `pyproject.toml` `nest.plugins.identity` entry point|
|`sign` / `verify(..., as_of)` / `resolve` / `rotate_key(new_seed) -> KeyId`|plugin — protocol surface + spec-exact return|
|`register_peer` / `register_peer_inception`|legacy path + pre-rotation inception (key + commitment)|
|`verify_continuity` / `apply_rotation`|rotation = signature + chain-tip + commitment recompute|
|`digest_alg="sha256"`|constructor flag; unknown algorithms rejected at construction|

Every module carries the Nanda Town idioms: SPDX header, `from __future__ import annotations`, and an `Example::` block on each public class and function. The plugin uses `nest_core` types (`AgentId`, `AgentIdentity`, `Signature`) and implements the identity layer's `sign` / `verify` / `resolve` surface.

## Threat model — 3 attacks + recovery

|#|Attack|Defeated by|Caught by|
|-|-|-|-|
|1|Post-rotation forgery (stolen retired key signs)|closed as-of window|`verify`; validator (a)/(b)|
|2|Backdating (new key claims old-window tick)|as-of window, verifier-supplied tick|`verify` + `_select_record`; validator (a)/(b)|
|3|Rotation hijack (current key installs chosen successor)|revealed key must hash to prior commitment|`verify_continuity` / `_commitment_matches`; validator (c)/(d)|
|—|Recovery|rotate to pre-committed cold key, sign validly|validator (e)|

## How this PR relates to the merged rotati

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/71/head:pr-71
git checkout pr-71