How API Key Rotation Affects a Verified Track Record
What happens to a NakedPnL hash chain when a trader rotates or revokes their read-only API key, with worked examples of credential fingerprints and chain continuity.
- Rotating a read-only API key is a security best practice and does not invalidate a NakedPnL track record.
- The credential fingerprint stored in CryptoTraderCredential.keyFingerprint is hashed from the key material and rotates with each new key.
- Chain continuity is preserved because contentHash is computed over the venue API response, not over the API key itself.
- If a key is revoked mid-track-record, the chain pauses, the trader transitions to STALE status, and no synthetic NAV is invented.
- A rotation event is recorded in the credential audit log so verifiers can see exactly which dates used which key fingerprint.
API keys should rotate. Every reputable security framework — NIST SP 800-63B, the CIS Critical Security Controls, OWASP's API Security Top 10 — treats long-lived credentials as a risk to be minimized. NakedPnL agrees, and the verification pipeline is built so that rotating a read-only exchange API key does not invalidate the published track record. This guide explains how that property is achieved, what the trader and the verifier each see during a rotation, and what happens at the edge cases of full revocation and silent disablement.
The short version is that the contentHash that anchors every row of the chain is computed over the venue's raw API response, not over the API key. The key is a transport credential, not a hashed input. As long as the new key returns the same kind of response from the same account, the chain stays continuous. The detailed version requires walking through how credentials are stored, how the cron uses them, and what the audit log preserves.
Why rotate keys at all
Read-only API keys are still keys. If a key file leaks from a backup tarball, ends up in shell history, or is stolen by malware, an attacker cannot withdraw funds — but they can read account state, infer position size, and feed that information into front-running models. Most exchanges allow rotating keys at any time without affecting the underlying account.
- A laptop compromise where the user reasonably suspects credential exfiltration.
- A scheduled rotation policy (every 90 or 180 days is common in regulated environments).
- An exchange-driven rotation when a venue deprecates an old auth scheme (Binance HMAC-SHA256 to Ed25519 was a recent example).
- A change of read-only scope, such as adding futures account permissions to a spot-only key.
- Moving the key from a personal account to a service account managed by a fund's IT team.
How NakedPnL stores credentials
Each connected venue has a row in CryptoTraderCredential. The cleartext key material is encrypted at rest with envelope encryption — a per-record data key encrypted by a KMS-managed master key. The plaintext is only decrypted in memory inside the daily snapshot cron and the cleartext is never logged. Two metadata fields are stored alongside the ciphertext: keyFingerprint and createdAt.
The keyFingerprint is SHA-256 of the public portion of the credential — for HMAC keys, the API key id; for Ed25519 keys, the public key. The fingerprint is short (16 hex characters in the audit log) and not reversible into the key material. It is the single value that lets a verifier or auditor see when a rotation happened without exposing any secret.
model CryptoTraderCredential {
id String @id @default(cuid())
traderId String
venue Venue
encryptedKey Bytes // envelope-encrypted at rest
keyFingerprint String // SHA-256 of the public key id
status CredentialStatus // ACTIVE | ROTATED | REVOKED
createdAt DateTime
rotatedAt DateTime?
trader CryptoTrader @relation(fields: [traderId], references: [id])
@@index([traderId, venue, status])
}What the daily cron actually hashes
The 23:55 UTC snapshot cron decrypts the active credential, calls the venue adapter, and receives a raw response. For Binance that is a JSON object containing the wallet balance and equity. For IBKR it is a Flex Web Service XML report. For Polymarket it is a subgraph response merged with a REST positions response. The adapter normalizes the venue-specific quirks but preserves the bytes that materially describe the account state at that moment.
Those bytes are the input to the contentHash. The credential is the transport layer, not the payload. As long as a rotated key produces the same shape of response from the same account, the chain stays continuous. If the new key has different read scope (for example it lacks futures account access), the response shape changes and the contentHash changes — but that is correct behavior, because the on-record account state has genuinely changed.
async function snapshot(trader: CryptoTrader, when: Date) {
const cred = await getActiveCredential(trader);
// Key material decrypted in memory only.
const raw = await venueAdapter[cred.venue].fetchBalance(cred);
const canonical = canonicalize(raw);
const contentHash = sha256(canonical);
const previous = await getChainHead(trader);
const previousChainHash = previous?.chainHash ?? "genesis";
const chainHash = sha256(previousChainHash + contentHash);
return prisma.navSnapshot.create({
data: {
traderId: trader.id,
sequence: (previous?.sequence ?? -1) + 1,
snapshotDate: when,
navUsd: deriveNav(raw), // reported NAV
contentHash, // bound to raw response
chainHash, // bound to previous + content
previousHash: previousChainHash,
credentialFingerprint: cred.keyFingerprint, // for the audit log
},
});
}Worked example — a rotation in week 12
Consider a trader who connects on day 1 and rotates their Binance key on the morning of day 84. The trader generates a new HMAC pair on Binance, pastes it into the NakedPnL key-rotation form, and the old key is automatically revoked at Binance immediately after the new key successfully fetches a balance. Here is the audit-log slice for the relevant days.
| Date | Sequence | NAV USD | Credential FP | Content hash | Chain hash |
|---|---|---|---|---|---|
| 2026-04-26 | 82 | 18,902.41 | f1c9...3b22 | 9d22...0aa1 | 4711...92c0 |
| 2026-04-27 | 83 | 18,440.18 | f1c9...3b22 | 8c80...11d4 | 12fa...7e90 |
| 2026-04-28 | 84 | 19,015.77 | a772...b09c | 21ee...ff03 | 61b8...2d31 |
| 2026-04-29 | 85 | 19,144.02 | a772...b09c | 5320...d7ac | 9cd1...4011 |
The credential fingerprint changes between sequence 83 and 84. The contentHash on day 84 is computed over a Binance response fetched with the new key, but it is structurally the same kind of payload as before, so chain continuity is preserved. A verifier running the script from the verification tutorial will recompute every chainHash all the way through and will see no break.
What happens when a key is revoked mid-track-record
Revocation without replacement is the harder edge case. Suppose the trader revokes their Binance key at the venue without uploading a replacement to NakedPnL. The 23:55 UTC cron will call the adapter, the adapter will receive an HTTP 401 or a Binance-specific error code, and the snapshot will fail. NakedPnL never invents NAV.
- The cron records a SnapshotFailure row with the venue error and a backoff schedule.
- After three consecutive failures (typically 72 hours), the trader transitions to STALE status. Their registry rank is suspended.
- If a replacement key is supplied within seven days, the cron resumes from the next UTC date. The chain has a gap in calendar time but no gap in sequence numbers — sequence is contiguous, snapshotDate is not.
- If no replacement key is supplied within 30 days, the track record is marked PAUSED and removed from the public registry until the trader reconnects. The chain rows already written remain verifiable forever.
- If the trader chooses never to reconnect, the existing chain is preserved as a sealed track record. It can still be hash-verified and OpenTimestamps-verified, but it accrues no new rows.
The key invariant is that calendar gaps caused by venue access loss do not become silent zeros. Time-weighted return is calculated only over snapshots that actually exist; missing days are not interpolated, smoothed, or estimated. A six-month track record with a four-week gap will display the gap explicitly on the trader's profile rather than hiding it.
What rotation looks like to the trader
From the trader's perspective rotation is a guided form at /settings/credentials. The flow validates the new key by performing a single live balance fetch before persisting it, atomically swaps the active credential, and emails an audit summary with the old and new fingerprints. The previous credential row is updated with status=ROTATED and rotatedAt, but is retained in the database so the audit log can show fingerprint history without exposing key material.
curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
https://nakedpnl.com/api/admin/credentials/cpx-trader/audit \
| jq '.events[]'
# {
# "date": "2026-04-28T08:14:02Z",
# "kind": "ROTATED",
# "venue": "BINANCE",
# "oldFingerprint": "f1c9...3b22",
# "newFingerprint": "a772...b09c"
# }Common questions allocators ask
When NakedPnL is used as a due-diligence input, allocators frequently ask whether key rotations weaken the track record. The answer is the opposite: a clean rotation log is an indicator of mature operational hygiene. A track record where the same key has been live for three uninterrupted years is more concerning, not less, because it suggests the trader does not have a rotation policy.
Allocators also ask whether NakedPnL could be tricked by a trader rotating in a key from a different account. The answer is that the contentHash on the post-rotation snapshot is computed over a response that contains the venue's account identifier. A rotation to a key for a different account would produce a contentHash chained from a discontinuous balance, which would show up as a step-function jump on the equity curve and would trigger the same outlier detection that catches deposit/withdrawal events.
Summary
- API key rotation is encouraged. NakedPnL is built so that rotating preserves the chain.
- The contentHash hashes the venue API response, not the API key, which is why rotation is invisible to chain continuity.
- Each credential carries a SHA-256 fingerprint that is exposed in the per-snapshot audit log so verifiers can see which key produced which row.
- Revocation without replacement triggers STALE status after 72 hours and PAUSED status after 30 days. No synthetic data is ever written.
- Existing chain rows remain verifiable forever, even if the trader never reconnects.