1# VAULT-ADOPTION Integration Contract23Source of truth for integrating `/Users/june/Downloads/peach-vault-adoption-brief.md`.4Where this contract and an individual draft disagree, this contract wins.56## Scope78Draft only. No production source was modified in this pass. Integrate in9browser first, then coordinate Android. `peach-sync` has no adoption work.1011Current browser drift from the brief:12- Brief base says v4.0.3; current dev is v4.0.13.13- `PAIR_IDENTITY` already supports optional `vault_id` and Pro `license`.14- Sync pairing is already usable by non-Pro devices; at least one device needs15 Pro and license transfer already rides normal pairing.1617## Files in `src/drafts/VAULT-ADOPTION/`1819| Draft file | Destination / role |20|---|---|21| `README.md` | Draft index; keep or trash after integration. |22| `vault-createVaultFromRootKey.ts` | Code hunks for `src/background/vault.ts`. |23| `vault-createVaultFromRootKey.test.ts` | Destination `src/background/vault-createVaultFromRootKey.test.ts`; T-A01..T-A05. |24| `messages-CREATE_VAULT_FROM_ROOT_KEY.ts` | Code hunks for `src/shared/types.ts` and `src/background/messages.ts`. |25| `messages-CREATE_VAULT_FROM_ROOT_KEY.test.ts` | Destination `src/background/messages.create-vault-from-root-key.test.ts`; T-A06..T-A09. |26| `chirp-adopt.ts` | Destination `src/shared/chirp-adopt.ts`. |27| `chirp-adopt.test.ts` | Destination `src/shared/chirp-adopt.test.ts`; T-A10..T-A15. |28| `AdoptionConsentModal.tsx` | Destination `src/popup/components/AdoptionConsentModal.tsx` plus CSS hunk to `src/popup/popup.css`. |29| `AdoptionConsentModal.test.tsx` | Destination `src/popup/components/AdoptionConsentModal.test.tsx`; T-A16..T-A20. |30| `onboarding-adopt-path.tsx` | Destination `src/popup/views/AdoptVaultView.tsx` plus hunks for `Onboarding.tsx`, `Lock.tsx`, `App.tsx`. |31| `onboarding-adopt-path.test.tsx` | Destination `src/popup/views/AdoptVaultView.test.tsx` plus one contracts test; T-A21..T-A25. |32| `chirp-pair-adopt-source.ts` | Hunk for `src/popup/chirp-pair.ts`; requires a supporting responder-open-frame export from `src/shared/chirp-handshake.ts`. |33| `chirp-pair-adopt-source.test.ts` | Destination `src/popup/chirp-pair.adopt-source.test.ts`; T-A26..T-A29. |34| `INTEGRATION-CONTRACT.md` | This file. |3536## Integration Order3738Follow phases 1 -> 2 -> 3 -> 4 -> 5 -> 6. Save each phase separately.39401. **Phase 1: vault root-key adoption**41 - Add `wrapExistingRootKeyForRecovery` and `createVaultFromRootKey` to42 `src/background/vault.ts`.43 - Import `computeVaultId`.44 - Run `npx tsc --noEmit`, relevant vault tests, then save.45462. **Phase 2: message handler**47 - Add `CREATE_VAULT_FROM_ROOT_KEY` to `MessageType`.48 - Add handler beside `CREATE_VAULT`.49 - Handler is vault-less allowed and returns `{ ok: true, vaultId }`.50 - Run message tests and save.51523. **Phase 3: adoption sub-protocol**53 - Add `src/shared/chirp-adopt.ts`.54 - Add tests T-A10..T-A15.55 - Important correction from the brief: receiver computes and checks56 `computeVaultId(root_key_hex)` before creating the vault. The brief's57 original order would allow partial local creation before detecting a58 mismatch.59604. **Phase 4: source consent UI**61 - Add `AdoptionConsentModal`.62 - Add amber CSS to `popup.css`.63 - Wire the modal through the source pairing UI event path.64 - Re-auth uses `VERIFY_MAIN_PASSWORD` or the existing `vault.verifyMainPassword`65 background path before allowing `grantAdoption`.66675. **Phase 5: receiver onboarding**68 - Add `"adopting"` to `App.tsx` view state.69 - Add `AdoptVaultView`.70 - Add final onboarding action: `I already have Peach ->`.71 - Receiver password is collected before chirp. Use `CREATE_VAULT_FROM_ROOT_KEY`72 only after SAS confirm and `ADOPT_GRANT` validation.73746. **Phase 6: source routing**75 - Refactor `chirp-handshake.ts` so responder can inspect the first encrypted76 JSON frame as `PAIR_IDENTITY` or `ADOPT_REQUEST`.77 - Normal pairing remains unchanged for `PAIR_IDENTITY`.78 - `ADOPT_REQUEST` runs `grantAdoption` only after SAS and consent pass.79807. **Version/build save**81 - Bump browser version from current dev version by at least PATCH.82 - Run `npm run build` after `tsc`/vitest.8384## New Message Types8586Add to `src/shared/types.ts`:8788```ts89| "CREATE_VAULT_FROM_ROOT_KEY"90```9192Payload:9394```ts95{96 password: string;97 rootKeyHex: string;98}99```100101Response:102103```ts104{ ok: true; vaultId: string } | { ok: false; error: string }105```106107Suggested error strings:108- `invalid_payload`109- `ADOPT_INVALID_PASSWORD`110- `ADOPT_INVALID_ROOT_KEY: expected 64 lowercase hex chars`111- `ADOPT_VAULT_ID_MISMATCH`112113## New Frame Types114115Add in `src/shared/chirp-adopt.ts`:116117```ts118ADOPT_REQUEST119ADOPT_GRANT120ADOPT_IDENTITY121```122123Do not overload `PAIR_IDENTITY`. Adoption is a sub-protocol that runs on the124same Noise/SAS channel after chirp, with its own two-phase persistence rules.125126## New View State127128Add to `App.tsx`:129130```ts131| "adopting"132```133134`"adopting"` is vault-less allowed. If adoption succeeds:135- Refresh `HAS_VAULT`.136- Mark onboarding complete.137- Open the normal unlocked app shell.138- Trigger an immediate sync attempt if a paired source was persisted.139140## Estimated Test Count14114229 specs:143- T-A01..T-A05: vault creation from adopted root key.144- T-A06..T-A09: message handler.145- T-A10..T-A15: adoption sub-protocol.146- T-A16..T-A20: source consent modal.147- T-A21..T-A25: receiver onboarding path.148- T-A26..T-A29: source routing.149150## Cross-Platform KAT Extensions151152Add a fixed adoption KAT shared with Android:153- Fixed chirp ephemeral inputs.154- Fixed source root key: 32 bytes.155- Fixed source identity keys.156- Expected SAS.157- Expected `vault_id`.158- Expected frame sequence:159 `ADOPT_REQUEST -> ADOPT_GRANT -> ADOPT_IDENTITY`.160161The KAT should prove Android and browser serialize the same snake_case frame162fields and derive the same vault id from the same root key bytes.163164## Android Work Required165166Do not implement in this browser draft, but the Android integration pass needs:167- Pairing protocol layer: add `ADOPT_REQUEST`, `ADOPT_GRANT`, `ADOPT_IDENTITY`.168- Vault layer: create local vault from imported root key with local password.169- Source consent UI: warning modal with master-password re-auth.170- Receiver onboarding: "I already have Peach" path, local password first.171- License transfer: send/activate optional `license` in adoption frames, matching172 browser's normal pairing transfer behavior.173- Tests mirroring T-A01..T-A29 plus the cross-platform KAT.174175Likely files in peach-android (names may differ):176- pairing/chirp protocol module177- vault repository / crypto storage module178- onboarding Compose screens179- source consent Compose dialog180- paired-device repository181- license activation path182183## Security Checklist184185- [x] `root_key_hex` is validated as exactly 64 lowercase hex chars before use.186- [x] Decoded root key bytes are zeroed after `createVaultFromRootKey` returns.187- [x] Drafts never log `root_key_hex`.188- [x] `ADOPT_GRANT` is sent only after the regular SAS gate.189- [x] `ADOPT_GRANT` is sent only after source master-password re-auth succeeds.190- [x] Source does not persist the receiver until `ADOPT_IDENTITY` is received.191- [x] Receiver aborts before creating a vault if `vault_id` does not match the192 granted root key.193- [x] Receiver stays vault-less on invalid root key, unexpected frame, SAS194 cancel, source consent reject, and vault-id mismatch.195- [x] `ADOPT_REQUEST` from a peer without adoption support results in an196 unexpected-frame/error path, not a grant.197- [x] Consent modal copy says "copies your entire vault including all passwords198 and Pro".199200## Known Integration Corrections201202- The brief suggests `rootKeyHex.split('').fill('0')`; that does not zero the203 original immutable string. Integrate byte-buffer zeroing and keep string scope204 narrow.205- `createVaultFromRootKey` must set `rootKey = new Uint8Array(rootKeyBytes)`,206 not `rootKey = rootKeyBytes`, because the input buffer is wiped in `finally`.207- The current `runPairingResponder` cannot handle adoption as-is because it208 decodes the first encrypted frame as `PAIR_IDENTITY`. Refactor around an209 open-frame responder primitive, then rebuild normal pairing on top.210