# Signers

The calendar signs its anchor transactions with one key at a time. We call each key's run a signer epoch, number the epochs from 1 and list every one the calendar has used at `/v1/signers`.

## The Signer List

The page fills this list in the browser from [https://etchd.app/v1/signers](https://etchd.app/v1/signers).

All times are UTC. `firstTick` is the first tick the key was able to anchor, the oldest one that no transaction held when the key took over. `lastTick` is the highest tick it anchored. For the key that signs now, both end fields stay empty.

Epoch 1 is the launch key, a temporary hot wallet we funded with 0.1 ETC to sign until we rotate to a long-term key, and whichever key is signing, the signer refuses to spend more than 0.05 ETC in one UTC day.

## The Accepted Window

A verifier with the list looks up the sender of each anchor transaction. The key counts as known only when the anchor block's time falls inside its window, from 60 seconds before `activeFrom` to 900 seconds after `activeTo`, with no upper bound while the key is current. The 60 seconds allow for block times that run behind the calendar's clock, and the 900 seconds for a transaction still in flight when a key is retired.

A listed key used outside its window comes back as `known: false` with `outsideWindow: true`. A leaked key shows up this way once the calendar has rotated away from it, and its later transactions stop passing as ours.

Each key is accepted from 60 seconds before activeFrom to 900 seconds after activeTo. Anchors are named by epoch and nonce. A transaction from the epoch 1 key after its window comes back as known: false.

The verifier enforces the list only when you pass the flag shown under [Checking the Sender](https://etchd.app/docs/verifying#sender), and it applies the window by the rules of [Signer Epochs](https://github.com/classix-dev/etchd/blob/main/SPEC.md#signer-epochs) in SPEC.md.

## How Rotation Works

A rotation moves anchoring to a new key and changes no commitment. Old proofs verify as before, because tick headers and calldata carry no epoch.

1. The operator makes the new key offline and installs it on the signer Worker as `ETCHD_SIGNER_KEY_NEXT`. Nothing signs with it yet. `/v1/status` shows it as `signer.next`.
2. The new address is funded from the cold wallet. Rotation refuses a key that holds less than one day of spend, 0.033264 ETC.
3. An admin call to `/v1/admin/rotate` stops new batches, waits for the transaction in flight to be mined, reads the new key's balance and nonce from the calendar's four ETC RPCs (the `ETC_RPCS` list in [`packages/worker/wrangler.jsonc`](https://github.com/classix-dev/etchd/blob/main/packages/worker/wrangler.jsonc)) with a quorum of two, switches the signer and records the new epoch. If anything fails the old key keeps signing.
4. The new epoch appears here with an open end, and the next anchor transaction on Blockscout comes from the new address. Because each key counts its own nonces from 0, an anchor transaction is named by epoch and nonce, as in `/v1/anchors/2/0`.

The old address keeps about 0.001 ETC for 30 days, because in that time the daily reorg audit can send an old-epoch transaction back to the mempool with its stored bytes and mining it again costs gas from that address.

If a key leaks, a watchdog halts anchoring and the rotation runs while halted, as [The Anchor Key](https://etchd.app/docs/trust#keys) in the Trust Model describes. Operators run it by the steps in [Key Rotation](https://github.com/classix-dev/etchd/blob/main/DEPLOY.md#key-rotation) in DEPLOY.md, and the Anchor code is in [`packages/worker/src/anchor/anchor.ts`](https://github.com/classix-dev/etchd/blob/main/packages/worker/src/anchor/anchor.ts).
