01-vault-id-in-pair-identity.md

6.3 KB · raw

rendered · source

1# Change 1 — Add `vault_id` to the PAIR_IDENTITY frame23> **DRAFT.** Conforms to `_GROUND-TRUTH.md`. Target file:4> `/Users/june/projects/peach-browser/src/shared/chirp-handshake.ts`.56## Goal7Have the extension **send** `vault_id` in the `PAIR_IDENTITY` frame so the8desktop helper can persist which vault it was paired to. peach-sync already9accepts `vault_id` as an **optional** field10(`peach-sync/src/pairing.zig:54`, tolerated/ignored when absent), so this is11purely additive and backwards-compatible.1213## Backwards-compatibility statement14- `vault_id` is `?[]const u8 = null` in peach-sync and parsed with15  `ignore_unknown_fields` (`pairing.zig:54,57,167`); it is consumed defensively16  (`orelse ""`, persisted only when non-empty — `pairing.zig:209,212-214`).17- Existing chirp peers (mobile Android/iOS) that **omit** `vault_id` keep18  working unchanged — the field is optional on both the TS interface and the19  helper struct.20- The helper does not send `vault_id` back (`pairing.zig:173-180`), so the21  extension's `decodeFrame` does not require it on receive. The patch below only22  affects the OUTBOUND frame.2324## Privacy consideration (carry the existing note forward)25`vault_id` is the same stable, LAN-broadcast identifier documented at26`src/shared/sync-identity.ts:62-74` (PRIVACY NOTE / PCH-02-006): it is stable27across sessions and lets a passive LAN observer link every device sharing a28vault. Putting it in `PAIR_IDENTITY` keeps it inside the **encrypted** NK29transport, so this change does not add network exposure beyond what mDNS sync30discovery already leaks. The patch comment references that note rather than31restating it.3233## Threading: where `vault_id` enters the handshake34`computeVaultId` is async and needs the in-memory `masterKey`35(`sync-identity.ts:75-82`), neither of which is in scope inside36`runPairingInitiator`/`runPairingResponder`. The clean seam is the existing37`LocalIdentity` argument already passed into both functions: add an optional38`vaultId` field, computed by the caller that already holds the master key (the39pairing-flow entry point in the popup/background that builds `LocalIdentity`).40No master key is introduced into `chirp-handshake.ts`.4142- Caller obligation (documented, not patched here): the code that constructs43  `LocalIdentity` for pairing must call44  `vaultId = await computeVaultId(masterKey)` (`sync-identity.ts:75`) and set it45  on the identity. If the caller has no master key (locked vault), it leaves46  `vaultId` undefined and the field is omitted — still valid.4748---4950## Patch (apply to `src/shared/chirp-handshake.ts`)5152### Hunk A — extend `LocalIdentity` with optional `vaultId`53Context: lines 43-51.54```diff55 export interface LocalIdentity {56   deviceId: string;57   deviceName: string;58   deviceType: DeviceType;59   longTermPublicKeyHex: string;60   signingPublicKeyHex?: string;61   /** Server-only field — Android/iOS/desktop sync hosts announce their port. */62   wsPort?: number;63+  /**64+   * Stable per-vault id (`computeVaultId`, sync-identity.ts). Sent in the65+   * outbound PAIR_IDENTITY so a desktop helper can record which vault it66+   * paired to. Optional: the caller omits it when the vault is locked (no67+   * master key). Privacy: this is the LAN-linkable id — see the PRIVACY NOTE68+   * at sync-identity.ts:62 (PCH-02-006).69+   */70+  vaultId?: string;71 }72```7374### Hunk B — add `vault_id` to the `PairingFrame` interface75Context: lines 53-61.76```diff77 interface PairingFrame {78   type: "PAIR_IDENTITY";79   device_id: string;80   device_name: string;81   device_type: DeviceType;82   long_term_public_key_hex: string;83   signing_public_key_hex?: string;84   ws_port?: number;85+  // Optional: present only when the sender knows its vault id. peach-sync86+  // treats this as optional (pairing.zig:54); chirp mobile peers omit it.87+  vault_id?: string;88 }89```9091### Hunk C — emit `vault_id` from the INITIATOR frame92Context: lines 106-114 (`runPairingInitiator`).93```diff94   const ourFrame = encodeFrame({95     type: "PAIR_IDENTITY",96     device_id: ourIdentity.deviceId,97     device_name: ourIdentity.deviceName,98     device_type: ourIdentity.deviceType,99     long_term_public_key_hex: ourIdentity.longTermPublicKeyHex,100     signing_public_key_hex: ourIdentity.signingPublicKeyHex,101     ws_port: ourIdentity.wsPort,102+    vault_id: ourIdentity.vaultId,103   });104```105106### Hunk D — emit `vault_id` from the RESPONDER frame107Context: lines 209-217 (`runPairingResponder`).108```diff109   const ourFrame = encodeFrame({110     type: "PAIR_IDENTITY",111     device_id: ourIdentity.deviceId,112     device_name: ourIdentity.deviceName,113     device_type: ourIdentity.deviceType,114     long_term_public_key_hex: ourIdentity.longTermPublicKeyHex,115     signing_public_key_hex: ourIdentity.signingPublicKeyHex,116     ws_port: ourIdentity.wsPort,117+    vault_id: ourIdentity.vaultId,118   });119```120121> `encodeFrame` is `JSON.stringify` (line 63-65): when `vaultId` is `undefined`,122> `JSON.stringify` **drops the key entirely**, so the omitted-field123> backwards-compat behaviour is automatic — no extra guard needed.124125### (Optional) Hunk E — validate `vault_id` on decode if present126`decodeFrame` (lines 67-85) currently ignores extra fields. The helper never127sends `vault_id`, so receive-side validation is **not required** for desktop128pairing. If a future symmetric flow has the peer send it, add a lenient guard:129```diff130   if (131     parsed.signing_public_key_hex !== undefined &&132     !/^[0-9a-f]{64}$/.test(parsed.signing_public_key_hex)133   ) {134     throw new Error("signing_public_key_hex is not 32-byte hex");135   }136+  // vault_id, when present, is a 64-char lowercase hex BLAKE3 digest137+  // (computeVaultId). Lenient: only validate shape if the peer sent one.138+  if (parsed.vault_id !== undefined && !/^[0-9a-f]{64}$/.test(parsed.vault_id)) {139+    throw new Error("vault_id is not 32-byte hex");140+  }141   return parsed as PairingFrame;142```143**Recommendation:** ship Hunks A–D only for the desktop-pairing milestone; defer144Hunk E until a peer actually sends `vault_id`, to avoid over-constraining.145146## Notes for the implementer147- `vault_id` format is the 64-char lowercase hex BLAKE3 output of148  `computeVaultId` (`sync-identity.ts:28,81`). Keep it lowercase hex to match149  `SYNC_HELLO`'s `vaultId` so the helper's later vault check150  (`sync_coordinator.zig:141`) compares equal.151- Do not log `vault_id`. It is the LAN-linkable id (privacy note above).