This commit is contained in:
Vitor Pamplona 2024-12-11 17:32:09 +01:00 committed by GitHub
commit 82c95ef04e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

162
82.md Normal file
View File

@ -0,0 +1,162 @@
NIP-82
======
Medical Data
------------
`draft` `optional`
This NIP defines event kinds that encode health data inside Nostr in the form of FHIR Resources. They can be encoded in plain text using kind `82` or encrypted to the destinations using kinds `32225` and `32226`.
The idea is to decentralize medical information. Today medical data is either in the hands of the government or in the hands of gigantic private enterprises. Patients don't have any access, or control, over their data. That has to change. And Nostr can make it happen.
## FHIR and Medical Information
In FHIR, a Resource is a common base class for all types of medical information. Health care data is broken down into categories such as [patients](https://www.hl7.org/fhir/patient.html), [medication](https://www.hl7.org/fhir/medication.html), and [insurance claims](https://www.hl7.org/fhir/claim.html), among many others. Each of these categories is represented by a FHIR Resource, which defines the component data elements, constraints on data, and data relationships that together make up an exchangeable patient record.
The following json is how a [Patient](https://www.hl7.org/fhir/patient.html) entry is represented as a Resource:
```json
{
"resourceType": "Patient",
"id": "patient:0",
"active": true,
"name": [
{
"family": "BROOKS",
"given": [ "ALBERT" ]
}
]
}
```
These resources can be modified by EHRs at any time and are found by the local id (`patient:0` in the example).
An [immunization](https://www.hl7.org/fhir/immunization.html), for instance, is represented as another FHIR Resource as follows:
```json
{
"resourceType": "Immunization",
"id": "resource:2",
"status": "completed",
"vaccineCode": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/cvx",
"code": "207"
}
]
},
"patient": {
"reference": "Patient/patient:0"
},
"occurrenceDateTime": "2021-01-29",
"lotNumber": "0000007",
"performer": [
{
"actor": {
"display": "ABC General Hospital"
}
}
]
}
```
## Plaintext Event
Event kind `82` accepts a JSON-stringified FHIR Bundle directly on its `.content`.
The example below contains a Vision Prescription.
```json
{
"id": "6139d0c98dc9e24f9359faa6bf7ec34e2e793d51a1d69909e4078e29be8c7445",
"kind": 82,
"pubkey": "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
"created_at": 1725747978,
"tags": [[ "p", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" ]],
"content": "{\"resourceType\":\"Bundle\",\"id\":\"1\",\"type\":\"document\",\"entry\":[{\"id\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\",\"resourceType\":\"Practitioner\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Pamplona\",\"given\":[\"Vitor\"]}]},{\"id\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\",\"resourceType\":\"Patient\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Pamplona\",\"given\":[\"Vitor\"]}]},{\"id\":\"1\",\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2024-09-07\",\"dateWritten\":\"2024-09-07\",\"patient\":{\"reference\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\"},\"prescriber\":{\"reference\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"cylinder\":null,\"axis\":null,\"add\":null},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":null,\"axis\":null,\"add\":null}]}]}",
"sig": "3ca0879bd26b3e3064544a5b34b919bc62fc1e89202dd8143565c9602c26c960f12ab86ff022c82e5246c74c035bb5b8430238e16763bd9795623520bb719135",
}
```
Kind `82`s SHOULD be [gift wrapped](59.md) using `kind:1059` to each receiving user for the best privacy.
Kind `82` can also be included inside [NIP-17](17.md) DMs via [nembed](https://github.com/nostr-protocol/nips/pull/1078) events.
## Encrypted Wraps with Consent Management
Event `kind:32225` carries **secret-encrypted** medical information (kind:`82`) in its `.content`.
It uses 2 secrets:
- A viewing key pair that can decrypt the event, but not add or remove new users.
- A signing key pair that grants the authority to resign the event and thus can add and remove users that can decrypt it.
The private keys of each key pair are shared among participants via encrypted `key`-tags to each receiver.
```js
val sign = nostr.generateKeyPair()
val view = nostr.generateKeyPair()
{
"pubkey": sign.publicKey
"kind": 32225,
"tags": [
["d", "<unique identifier>"]
["p", "<pubkey 1>", "<relay url>" ]
["p", "<pubkey 2>", "<relay url>" ]
["p", "<pubkey 3>", "<relay url>" ]
["key", "<pubkey 1>", nip44Encrypt(sign.privateKeyHex, sign.privateKey, "<pubkey 1>") ] // may add new people
["key", "<pubkey 2>", nip44Encrypt(sign.privateKeyHex, sign.privateKey, "<pubkey 2>") ] // may add new people
["key", "<pubkey 3>", nip44Encrypt(view.privateKeyHex, sign.privateKey, "<pubkey 3>") ] // view only
],
"content": nip44Encrypt("<kind82-event>", sign.privateKey, view.publicKey),
"sig": signWith(sign.privateKey)
// ...
}
```
Upon receiving this event, Clients SHOULD find the ciphertext for their own key and decrypt it with `nip44Decrypt(ciphertext, loggedInUser.privatekey, event.pubkey)` to get one of the private keys.
If the corresponding public key of the decrypted private key is the `.pubkey` of the event, this is the signing key and the user has resharing permissions. If not, this is a viewing key and can only decrypt the `.content`
Use the signing key to decrypt all the other `p`-tag keys and find a viewing key.
Once both keys are known, decrypt the `.content` with `nip44Decrypt(event.content, view.privatekey, event.pubkey)`
### Special Case: No Viewing Keys
When no user has view permissions only, there won't be a viewing key in the event. The `.content` MUST then be encrypted to the signer's own public key.
```js
val sign = nostr.generateKeyPair()
{
"pubkey": sign.publicKey
"kind": 32225,
"tags": [
["d", "<unique identifier>"]
["p", "<pubkey 1>", "<relay url>"]
["p", "<pubkey 2>", "<relay url>"]
["key", "<pubkey 1>", nip44Encrypt(sign.privateKeyHex, sign.privateKey, "<pubkey 1>") ] // may add new people
["key", "<pubkey 2>", nip44Encrypt(sign.privateKeyHex, sign.privateKey, "<pubkey 2>") ] // may add new people
],
"content": nip44Encrypt("<kind82-event>", sign.privateKey, sign.publicKey),
"sig": signWith(sign.privateKey)
// ...
}
```
### Security
Relays don't have access to private keys and thus cannot see the contents of this type. Client apps however, have a responsibility to NEVER display the secret in the UI and do not allow users to copy it outside of the event.
It is expected that Health Information will be kept in specialized relays due to the nature of health data regulations. By knowing the event kind, the relay operator knows this package contains health data and may accept or reject it according to its authorized activity.
### Editability of Content and Secrets
The author of a kind `32225` can not only change the resource at any time, but it can also change the secret that encrypts the content. If the secret leaks to unauthorized parties, the owner of the data can always individually reset the access to it.
It is expected that some jurisdictions require an author to periodically rotate these secrets while maintaining access to the relevant people.