# Key Transparency (V3.12)

> **Status:** v0.4.0+ — opt-in. Server runs unchanged when KT is off.
> Klient ignorerer proof-felt når KT-config mangler. Trygg å rulle ut
> uten klient-update.

Shades prekey-server er sannhetskilde for hvilket bundle som er
publisert for hver adresse. Uten Key Transparency (KT) kan en
ondsinnet eller kompromittert server bytte ut et bundle uten at noen
oppdager det. Med KT er hvert bundle som leveres **kryptografisk
forpliktet** i en append-only Merkle log som tredjeparts-witnesses kan
auditere.

Se også `docs/V3.12-DESIGN.md` for designnotat med trusselmodell og
beslutningsspor.

---

## Hva KT garanterer

| Angrep | Detektert? |
|---|---|
| Server gir Bob feil bundle for `alice` | **Ja** — inklusjons-proof matcher ikke |
| Server gir Bob og Charlie ulike bundles for `alice` | **Ja** — witness-gossip ser to STH-er på samme `tree_size` |
| Server skriver om historikk for å skjule tidligere svik | **Ja** — konsistens-proof feiler |
| Server signerer "stale" STH for å holde et tidsvindu åpent | **Ja** — klient avviser STH eldre enn `maxStaleMs` (default 24t) |
| Førstegangs-impersonering av en helt ny adresse | **Nei** — KT ser bare etter at adressen er i loggen, ikke at den er "riktig" person. Bruk V3.3 (fingerprint-gate) + V3.10 (social recovery) for det. |

---

## Operatør: skru på KT

KT er opt-in og krever:

1. **Et Ed25519 signing-keypair** for STH-signering. Dette er
   *operatørens* nøkkel og må beskyttes som en code-signing-key.
2. **En persistent KTLogStore.** I produksjon: `PostgresKTLogStore`.
   I test/dev: `MemoryKTLogStore`.
3. **At klienter pinner samme `logPublicKey`** OOB (typisk via
   `Shade.config`-bundling i appen).

### Generere signing-key

```sh
bun run scripts/generate-kt-key.ts > kt-key.json
```

(Eller kjør manuelt: `crypto.generateEd25519KeyPair()` i en Bun REPL.)
Lagre `privateKey` i operatørens secret-store. Distribuér `publicKey`
til klienter sammen med app-config.

### Boot serveren med KT

```ts
import { createPrekeyServerWithKT } from '@shade/server';
import { PostgresPrekeyStore, PostgresKTLogStore } from '@shade/storage-postgres';
import { SubtleCryptoProvider } from '@shade/crypto-web';

const crypto = new SubtleCryptoProvider();

const prekeyStore = await PostgresPrekeyStore.create(process.env.DATABASE_URL!);
const ktStore = await PostgresKTLogStore.create(process.env.DATABASE_URL!);

const { app, kt } = await createPrekeyServerWithKT({
  crypto,
  store: prekeyStore,
  keyTransparency: {
    store: ktStore,
    signingPrivateKey: loadFromSecret('SHADE_KT_SIGNING_PRIVATE_KEY'),
    signingPublicKey: loadFromSecret('SHADE_KT_SIGNING_PUBLIC_KEY'),
    heartbeatIntervalMs: 10 * 60 * 1000, // default; 0 = off
  },
});

export default { port: 3900, fetch: app.fetch };
```

Når KT er på blir disse rutene tilgjengelig:

| Route | Hva den returnerer |
|---|---|
| `GET /v1/kt/log_id` | `{ logId, publicKey }` (begge base64) |
| `GET /v1/kt/sth` | Siste signed tree head |
| `GET /v1/kt/sth/:treeSize` | Historisk STH for et bestemt tree_size |
| `GET /v1/kt/consistency?from=N1&to=N2` | Konsistens-proof N1 → N2 |

Bundle-fetch (`GET /v1/keys/bundle/:address`) får nå et `ktProof`-felt
i responsen.

### Migrasjon fra ikke-KT

KT er bakoverkompatibel:

1. Skru på KT-config i serveren. Restart.
2. Eksisterende klienter ignorerer proof-feltene (`ktProof`, `ktSth`).
3. Etter hvert som klienter oppgraderes med KT-config (`mode: 'observe'`),
   begynner de å verifisere.
4. Når øko-systemet er vant til det, eskalér klienter til
   `'observe-strict'` for å avvise prekey-server-svar uten proof.

Ved første boot scanner KT-tjenesten ikke automatisk eksisterende
prekey-store-tilstand inn i loggen. **Re-registrering** av eksisterende
adresser (dvs. en `POST /v1/keys/register`-runde fra hver klient) er
det som backfiller. For et større deployment: anbefalt at en operatør
varsler brukerne om å re-registrere innen et tidsvindue. Klienter som
ikke re-registrerer vil feile `observe-strict`-fetch til de får ny key
fra peer.

---

## Klient: skru på KT

```ts
import { createShade } from '@shade/sdk';

const shade = await createShade({
  prekeyServer: 'https://shade.example.com',
  address: 'alice',
  keyTransparency: {
    mode: 'observe-strict',                // eller 'observe'
    logPublicKey: KT_LOG_PUBLIC_KEY_BASE64, // eller Uint8Array
    maxStaleMs: 24 * 60 * 60 * 1000,        // default 24t
  },
});
```

`shade.getKTWitness()` returnerer `LightWitness`-instansen som
samler observerte STH-er. Bruk `.compare(otherSth)` for manuell
gossip-sjekk mot peers.

### `mode: 'observe'`

- Verifiserer proof når serveren leverer det.
- Skipper verifisering hvis `ktProof` mangler i bundle-respons.
- Anbefalt under første utrulling der ikke alle klienter har
  re-registrert ennå.

### `mode: 'observe-strict'`

- Krever proof på hver `200`-respons. Mangler proof → kast `KTVerificationError`.
- Krever proof på hver `404`-respons også (for absence/tombstone-pinning).
- Anbefalt produksjons-modus når KT-økosystemet er etablert.

---

## Witness / auditor

`@shade/key-transparency` eksporterer `LightWitness`. Et CLI-verktøy
eller backend-job kan bruke den slik:

```ts
import { LightWitness } from '@shade/key-transparency';
import { SubtleCryptoProvider } from '@shade/crypto-web';

const crypto = new SubtleCryptoProvider();
const witness = new LightWitness({
  crypto,
  logPublicKey: KT_LOG_PUBLIC_KEY,
  fetcher: {
    async fetchLatestSTH() {
      const r = await fetch('https://shade.example.com/v1/kt/sth');
      return r.json();
    },
    async fetchConsistencyProof(from, to) {
      const r = await fetch(`https://shade.example.com/v1/kt/consistency?from=${from}&to=${to}`);
      return r.json();
    },
  },
});

// Poll periodically (e.g. every 5 minutes)
setInterval(async () => {
  try {
    const sth = await witness.pollOnce();
    console.log(`Observed STH: tree_size=${sth.treeSize}, root=${Buffer.from(sth.rootHash).toString('hex').slice(0, 16)}`);
  } catch (err) {
    console.error('Witness alarm:', err);
    // Send to PagerDuty / Slack / whatever
  }
}, 5 * 60 * 1000);
```

Witness-koden detekterer:
- **Stale STH** — server publiserer ikke nye STH-er i tide.
- **Split view** — to STH-er ved samme `tree_size` med ulik root.
- **Re-write** — konsistens-proof feiler.
- **Wrong key** — `log_id` matcher ikke pinnet `logPublicKey`.

---

## Operatørkost (estimat)

For et deployment med:

- **100k registrerte adresser**
- **1 identitets-rotasjon per år** per bruker
- **52 replenish per år** (én i uka, *ikke* committed til loggen — bare register/delete er)

| Ressurs | Per år | Kommentar |
|---|---|---|
| Log-rader | ~100k | bare register/delete |
| Lagring (leaves+index) | ~25 MB | base64-kodet |
| STH-rows | ~52k | én per heartbeat (10 min) |
| STH-storage | ~7 MB | |
| CPU per STH | ~1ms | Ed25519-signing er trivielt |
| Bundle-fetch overhead | <2ms | inkluderer audit-path-bygg |

**Backup:** behandle KT-tabellene som "kan ikke gjenopprettes" data —
`shade_kt_leaves` har en database-trigger som forbyr UPDATE/DELETE i
PostgreSQL-implementasjonen. Backup-strategi:

- Daglig full backup av `shade_kt_*` tabellene.
- WAL-shipping anbefalt (tap < 60 s i verste fall).
- **Test recovery** kvartalsvis. Recovery-prosedyre står under.

---

## Recovery

### Scenario 1 — STH-signing-key tapt eller kompromittert

Loggen forblir konsistent (alle gamle STH-er er allerede signert), men
nye STH-er kan ikke signeres med samme key.

**Steg:**

1. Generer ny Ed25519-keypair.
2. Skriv inn et "rotation breaks here"-leaf i loggen (operasjon = 0x03
   på en spesiell `__log__`-adresse) — operasjonen er rent
   informativ, men gjør rotasjonen synlig i tree.
3. Re-konfigurer serveren med ny key. Restart.
4. Server publiserer en ny STH; den vil ha en ny `log_id` (siden
   `log_id = SHA-256(publicKey)`).
5. **Klienter må eksplisitt akseptere ny key.** Inntil de pinner ny
   `logPublicKey`, vil deres `LightWitness` kaste
   `KTLogIdMismatchError`. Operatør publiserer ny key OOB med
   "rotated from `<gammel logId>`"-melding signert med gammel key
   (siste handling før gammel key zeroizes).

### Scenario 2 — KT-database korrumpert / tapt før backup

Dette er **det verste utfallet**. Loggen er per design ikke
gjenopprettbar — å "rekonstruere" den fra prekey-store ville bryte
selve invarianten KT lover.

**Steg:**

1. Stopp serveren.
2. Deklarer en "log-restart event" via offentlig kanal (status-side,
   release-notes, Twitter, etc.) — inkluder timestamp, tapte tree_size
   (siste backup-bare snapshot om mulig), og ny `logPublicKey`.
3. Generer ny KT-keypair (ikke bruk gamle).
4. Boot serveren tom (tom `shade_kt_*` tabell). Første STH er fra
   `tree_size = 0`.
5. Be brukerne om å re-registrere identitetene sine. Klientene vil
   trigge V3.3 fingerprint-gate på første re-meldings-flyt etterpå
   siden rotasjons-fingerprintet endres.
6. Auditor-organisasjoner kan publisere "vi observerte gammel log
   inntil tree_size N, ny log starter på 0 fra T+0" — dette gir
   sluttbruker mulighet til å vurdere hvor stort hullet er.

**Beskytt mot dette:** WAL-shipping + off-site backup. Aldri kjør KT
med kun én database-instans uten replicas.

### Scenario 3 — Witness oppdager split-view

Witness kaster `KTSplitViewError` i `LightWitness.observe()` eller
`KTVerificationError` i transport. Dette betyr:

- Operatøren har enten
  (a) hatt en software-bug som signerte to ulike STH-er ved samme
      tree_size, eller
  (b) er kompromittert / ondsinnet.

**Operatør-handling:**

1. Pause `POST /v1/keys/register`, `DELETE`, og bundle-fetch
   umiddelbart (return 503).
2. Audit `shade_kt_sths` — hvis du finner to rader med samme
   `tree_size` men ulik `root_hash`, har serveren gjort feil. Dette er
   alvorlig — finn root cause før du fortsetter.
3. Kommuniser ut til brukerne. Forutsett at en angriper har vært
   inne; trigge en bredere reset (recovery scenario 2) hvis det er
   mistanke om tampering.

**Klient-handling:**

- `LightWitness` har allerede holdt brukeren tilbake.
- SDK-en surfacer feilen som `KTSplitViewError` til app-koden.
- App-en bør vise advarsel: "Operatørens server kan ikke verifiseres.
  Avstå fra sending av sensitive meldinger inntil videre."

---

## Sikkerhets-anbefalinger

1. **Kjør minst én uavhengig witness.** Operatørens egen "witness"
   teller ikke — det må være en separat prosess på separate
   infrastruktur eid av en separat aktør (community-medlem, security
   firm, e.l.).

2. **Pin `logPublicKey` i app-binær eller signert config.** En
   man-in-the-middle som kan bytte både prekey-server og KT-key
   fanges ikke av KT alene.

3. **Loggrotasjon krever menneske-i-løkken.** Ikke automatiser
   key-rotation for KT — den eksplisitte breaking-event er en feature.

4. **`maxStaleMs` bør samsvare med din heartbeat.** 24t default tåler
   en heartbeat-pause på opptil et døgn; senk til 1–4t hvis du har
   strenge krav til friskhet.

5. **`observe-strict` bør være standard når økosystemet er etablert.**
   Default `'observe'` er en operasjonell overgangsmodus, ikke et
   sluttmål.

---

## Kjente begrensninger

- **Federation mellom flere prekey-servere** er ikke støttet i V3.12.
  Hver Shade-deployment har én log eller ingen.
- **Sparse Merkle tree for adresse-index** brukes ikke i V3.12 —
  fravær-proof er foreløpig nabopar-bevis. <100 KB ved 100k adresser
  er akseptabelt; sparse tree blir relevant fra ~10M+ adresser.
- **One-time prekey-rotasjon committes ikke** til loggen. OTP er
  ephemerale og inkludering ville støy-fylle loggen. Dette betyr at
  en server som svarer med riktig identitet men feil OTP fanges ikke
  av KT — forsvar mot dette ligger i V3.3 fingerprint-gate (samme
  identitet) + sesjons-etableringens X3DH (feil OTP gir feil shared
  secret → første melding feiler decryption).

---

## Tester og test-vektorer

- `packages/shade-key-transparency/tests/` — RFC 6962-kompatibel
  Merkle-log + STH + index-proofs (58 tests).
- `packages/shade-server/tests/kt.test.ts` — server-integrasjon (8
  tests).
- `packages/shade-transport/tests/kt-transport.test.ts` — klient-
  verifikasjon over HTTP (4 tests).
- `packages/shade-transport/tests/kt-split-view-e2e.test.ts` —
  V3.12-akseptanse split-view-deteksjon (3 tests).
- `packages/shade-sdk/tests/kt.test.ts` — SDK-config + witness wiring
  (3 tests).

Totalt 76 tester dedikert til KT.
