Ever Patient Wallet
Patient-owned health-data integration: migration + edge function + miniapp UI + scanner widgets.
Status: Phase 0 — migration + edge function written, pending deploy. Phase 1 = miniapp UI + scanner widgets.
What it is: Integration between medOS-ultra and the Ever Sovereign Wallet mobile app for patient-owned health data. Unlike the room-stick integration (ever-wallet.md, which handles nurse-call alerts for nursing homes), this integration gives patients true ownership of their clinical files — X-rays, imaging, wound photos, scans — encrypted with keys only the patient controls.
Key principle: The patient’s private key is the ultimate decider. Hospitals are grantees, not owners.
Companion docs:
docs/integrations/ever-wallet.md— room-stick pairing (different use case)docs/products/identity-vpn.md— VPN key custody (different layer)
1. Architecture
┌─────────────────────────────────────────────────────────────┐
│ Patient's device (phone/tablet) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Ever Sovereign Wallet (mobile app) │ │
│ │ • X25519 keypair (patient-owned, never leaves device)│ │
│ │ • Ed25519 signing key (for grant envelopes) │ │
│ │ • Generates per-file AES-256-GCM CEKs │ │
│ │ • Wraps CEKs via nacl.box to hospital pubkeys │ │
│ │ • Presents QR / NFC at clinic touchpoints │ │
│ └────────────────────────┬──────────────────────────────┘ │
└───────────────────────────┼─────────────────────────────────┘
│ QR scan / NFC tap
│ (pubkey + grants + signature)
▼
┌─────────────────────────────────────────────────────────────┐
│ medOS-ultra (this repo) │
│ │
│ ┌── PatientWalletScanner widget ────────────────────────┐ │
│ │ Mounted in: registration / bedside / nurse-assisted │ │
│ │ Reads QR → POSTs to ever-patient-wallet-pair edge fn │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ ▼ │
│ ┌── Edge Fn: ever-patient-wallet-pair ──────────────────┐ │
│ │ 1. Verify Ed25519 envelope signature │ │
│ │ 2. Verify intake nonce (5-min, hospital-issued) │ │
│ │ 3. Upsert patient_wallet_identities │ │
│ │ 4. Insert hospital_grants (sealed-box wrapped CEKs) │ │
│ │ 5. Consume nonce, audit, return hospital pubkey │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ ▼ │
│ ┌── Supabase tables ───────────────────────────────────┐ │
│ │ patient_wallet_identities — wallet pubkey per patient│ │
│ │ hospital_grants — per-scope sealed-box CEK wraps │ │
│ │ patient_drive_pins — IPFS CIDs (bucket A + B) │ │
│ │ patient_wallet_intake_nonces — anti-replay tokens │ │
│ │ patient_wallet_audit — append-only event log │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ │ realtime subscription │
│ ▼ │
│ ┌── patient-drive-v2 miniapp ──────────────────────────┐ │
│ │ DynamicContentRenderer module on patient profile │ │
│ │ • Bucket A tab: ops docs (stickers, barcodes, forms) │ │
│ │ • Bucket B tab: patient assets (imaging, wounds) │ │
│ │ • Upload → encrypt → IPFS pin → grant record │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ever network replication (Bucket B only) │
│ │ │
└─────────────────────────┼───────────────────────────────────┘
▼
┌──── Ever Network (company-operated → hospital consortium) ────┐
│ Decentralized IPFS pin federation │
│ Patient CIDs replicated across network nodes │
│ Patient wallet is the discovery mechanism (no directory) │
└──────────────────────────────────────────────────────────────┘
2. Two Buckets — Different Ownership, Same Pipeline
| Bucket A: Hospital Operations | Bucket B: Patient Assets | |
|---|---|---|
| Examples | Surgery stickers, barcodes, OR labels, internal forms, scanned consents | X-ray, MRI, CT, wound photos, lab images, FHIR bundles |
| Encrypted with | Facility DEK (AES-256-GCM) via existing health_history_key_store |
Wallet-issued CEK, sealed-box wrapped to hospital pubkey via hospital_grants |
| Key authority | Hospital | Patient |
| Pin scope | Local IPFS only | Local IPFS + ever network |
| Lifecycle | Retention-policy purged | Patient-owned forever |
| Wallet involvement | None | Required |
| Bucket assignment | Declared at upload widget compile time (source_module config) |
Declared at upload widget compile time |
The upload widget decides the bucket. Surgery sticker module = always A. Imaging upload = always B. Wound photo = always B. No user-facing toggle.
3. Files
| File | Purpose |
|---|---|
infrastructure/medbase/migrations/20260525d_patient_wallet_identity.sql |
5 tables + RLS + audit triggers + realtime publication |
infrastructure/medbase/functions/ever-patient-wallet-pair/index.ts |
NaCl-based pairing handshake edge function |
web/packages/service-kit/src/patient-wallet/PatientWalletScanner.tsx |
QR/NFC scanner widget (TODO: Phase 1) |
web/packages/service-kit/src/patient-wallet/AssistedCreationLauncher.tsx |
Deep-link to wallet app for nurse-assisted onboarding (TODO: Phase 1) |
web/packages/miniapps/patient-drive/ |
Patient drive v2 miniapp — DynamicContentRenderer module (TODO: Phase 1) |
docs/integrations/ever-patient-wallet.md |
This file |
4. Merge Contracts with the Wallet App
Four contracts between medOS-ultra and the Ever Sovereign Wallet mobile app. These must be agreed bilaterally.
Contract 1: QR / NFC Payload (Patient → Hospital)
The patient’s wallet app generates this payload and encodes it as a QR code or NFC NDEF record.
{
"patient_id": "<local Mongo ObjectId>",
"wallet_pubkey": "<base64 32B X25519>",
"wallet_signing_pubkey": "<base64 32B Ed25519, optional if same as X25519>",
"grants": [
{
"scope_kind": "bucket",
"scope_value": "bucket_b",
"wrapped_cek": "<base64 nacl.box ciphertext>",
"wrapped_cek_nonce": "<base64 24B>",
"cek_alg": "aes-256-gcm",
"valid_until": "2027-01-01T00:00:00Z"
}
],
"envelope_signature": "<base64 Ed25519 detached sig over canonical payload>"
}
The canonical payload for signing is:
{
"patient_id": "...",
"facility_id": "...",
"wallet_pubkey": "...",
"intake_nonce": "...",
"grants": [{ "scope_kind": "...", "scope_value": null, "cid": null, "valid_until": null }]
}
The intake_nonce and facility_id are injected by the hospital scanner widget after reading the QR. The wallet app does NOT need to know the hospital’s identity in advance — the nonce is provided by the scanner UI at scan time.
Contract 2: Deep-Link (Hospital → Wallet App)
For nurse-assisted wallet creation when the patient doesn’t have the app yet.
everwallet://create
?return_to=https://medos.local/api/wallet-pair-callback
&intake_nonce=<short-lived nonce>
&hospital_pubkey=<base64 X25519>
&hospital_label=<UTF-8 facility name>
&patient_intake_id=<opaque reference>
The wallet app:
- Walks the patient through wallet creation (identity, biometric, etc.)
- Generates the keypair
- Creates an initial grant wrapping a CEK to the
hospital_pubkey - POSTs back to
return_towith the Contract 1 payload
Contract 3: Grant Post-Back (Wallet App → Hospital, async)
For grants issued after the initial pairing (e.g., patient shares a specific file later).
POST {SUPABASE_URL}/functions/v1/ever-patient-wallet-pair
Content-Type: application/json
<same body as Contract 1, with a fresh intake_nonce>
The edge function is idempotent on (patient_id, wallet_pubkey) for identity, and additive for grants.
Contract 4: Revocation Broadcast (Wallet → Network → Hospitals)
When the patient revokes a hospital’s access:
- Patient wallet sends revocation to the ever network gateway
- Gateway fans out to affected hospitals
- medOS-ultra receives via
ever-patient-wallet-revocationsedge function (TODO) - Sets
hospital_grants.revoked_at+revoked_reason - Hospital can no longer decrypt — but existing local pin stays for audit defensibility
This contract is network-layer mediated. medOS-ultra’s role is to honor the revocation, not to implement the broadcast protocol.
5. Data Model
patient_wallet_identities
One row per patient-wallet binding. Captured when patient presents QR/NFC.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
patient_id |
TEXT | Local Mongo ObjectId |
wallet_pubkey |
TEXT | Base64 32B X25519 |
wallet_signing_pubkey |
TEXT | Base64 32B Ed25519 (nullable if same as X25519) |
leading_hospital_id |
TEXT | Patient’s designated escrow hospital (nullable) |
paired_via |
ENUM | qr / nfc / nurse_assisted / bedside_scan |
status |
ENUM | active / rotated / revoked |
hospital_grants
What the patient granted this hospital, per scope. Contains sealed-box-wrapped CEK.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
patient_wallet_id |
UUID FK | → patient_wallet_identities |
hospital_pubkey |
TEXT | Which hospital X25519 pub the CEK was wrapped to |
scope_kind |
ENUM | all / bucket / folder / single_cid |
scope_value |
TEXT | e.g. bucket_b, folder:imaging |
wrapped_cek |
BYTEA | nacl.box ciphertext |
grant_signature |
BYTEA | Ed25519 sig by patient |
valid_until |
TIMESTAMPTZ | Null = no expiry |
revoked_at |
TIMESTAMPTZ | Set on revocation |
patient_drive_pins
One row per uploaded file. CHECK constraint enforces bucket semantics.
| Column | Type | Notes |
|---|---|---|
bucket |
ENUM | A (hospital ops) or B (patient assets) |
cid |
TEXT | IPFS content ID of ciphertext |
source_module |
TEXT | Which upload widget created this pin |
facility_dek_id |
UUID FK | Bucket A only → health_history_key_store |
grant_id |
UUID FK | Bucket B only → hospital_grants |
pinned_local / pinned_ever |
BOOL | Replication status |
patient_wallet_intake_nonces
Short-lived anti-replay tokens, 5-minute window.
patient_wallet_audit
Append-only event log. Auto-populated by triggers on grant/pin lifecycle.
6. Pairing Flows
Flow A: Registration Desk
- Registration staff opens patient intake form
- `` widget is mounted in the form
- Staff clicks “Scan wallet” → hospital generates
intake_nonce(5-min TTL) - Patient opens Ever Wallet app → presents QR code
- Scanner reads QR → injects
facility_id+intake_nonce→ POSTs to edge fn - Edge fn: verify sig, verify nonce, upsert identity, insert grants, return hospital_pubkey
- UI shows “Wallet paired” confirmation
Flow B: Bedside Doctor
Same flow, but `` is mounted in the bedside chart header. Doctor initiates the scan. paired_via = 'bedside_scan'.
Flow C: Nurse-Assisted Onboarding
- Patient doesn’t have the wallet app
- Nurse clicks “Create wallet for patient” in ``
- Deep-link opens:
everwallet://create?return_to=...&intake_nonce=... - If patient has a personal device: hand them the tablet / show QR to open wallet app
- If not: nurse helps on a shared device, patient completes onboarding
- Wallet app posts back to
return_towith Contract 1 payload - Edge fn processes as normal
7. Encryption Flow for File Upload (Bucket B)
Patient wallet (on device) medOS-ultra (hospital)
───────────────────────── ──────────────────────
1. Generate CEK (AES-256-GCM, 32B random)
2. Encrypt file: AES-GCM(CEK, IV, plaintext)
3. Wrap CEK: nacl.box(CEK, nonce,
hospital_pubkey, wallet_privkey)
4. Sign grant envelope (Ed25519)
5. Present QR with: {wrapped_cek,
nonce, scope, signature}
6. Scanner reads QR
7. POST to edge fn
8. Verify sig + nonce
9. Store grant (wrapped CEK)
10. Upload ciphertext to IPFS
11. Record pin in patient_drive_pins
12. (async) Replicate to ever network
To decrypt (hospital reads an imaging file):
- Look up
hospital_grantsrow by CID - Unwrap CEK:
nacl.box.open(wrapped_cek, nonce, wallet_pubkey, hospital_privkey) - Fetch ciphertext from IPFS by CID
- Decrypt:
AES-GCM.decrypt(CEK, IV || ciphertext)
To decrypt (patient on personal device):
- Patient’s wallet already has the CEK (it generated it)
- Fetch ciphertext from ever network by CID
- Decrypt locally
8. Relationship to Existing Systems
| System | Relationship |
|---|---|
health_history_key_store (20260524d) |
Bucket A still uses facility DEK from this table. Bucket B supersedes with wallet-issued CEK. |
health-history-cache edge fn |
Will be modified (Task #3) to consume wallet grants for Bucket B bundles instead of generating DEKs. |
ever-wallet-pair edge fn |
Sibling, not replacement. Room-stick pairing for nursing home alerts is a separate use case. |
patient-drive (service-kit) |
Will be replaced by patient-drive-v2 miniapp (Task #4). Old patient-drive stays for backwards compat until migration is complete. |
ever_network_patient_identity (20260524d) |
Pre-existing cross-hospital identity mapping. patient_wallet_identities is the patient-presented equivalent — no directory lookup needed. |
9. Security Model
- Patient is the key authority. Hospital cannot decrypt without a grant. Grant is revocable.
- Envelope signature prevents forgery. Every grant is Ed25519-signed by the patient’s wallet.
- Intake nonce prevents replay. 5-minute TTL, single-use, facility-scoped.
- Audit trail is append-only. All lifecycle events (pair, grant, revoke, pin, unpin) are recorded.
- RLS = service_role only. No direct browser access to these tables.
- Hospital pubkey is per-Supabase-project. Generated once, stored as env var. Rotation = new keypair + re-wrap existing grants (migration job).
- Recovery is network-layer. Patient designates a “leading hospital” for airgapped key escrow. medOS-ultra stores the designation but does not implement the escrow.
10. Phased Rollout
| Phase | Scope | Status |
|---|---|---|
| 0 | Migration + edge function + this doc | Done |
| 1 | PatientWalletScanner + AssistedCreationLauncher + patient-drive-v2 miniapp | Pending |
| 2 | Flip health-history-cache to consume wallet grants for Bucket B | Pending — blocked on Phase 1 |
| 3 | Ever network replication for Bucket B pins | Pending — needs wallet app team coordination |
| 4 | Revocation edge function (ever-patient-wallet-revocations) |
Pending — network-layer dependency |
| 5 | Hospital consortium federation (hospitals join as peers) | Future — company-operated first |