Updates Medical Data spec to the NIP-44 and new event sharing techniques based on the secret-sharing technique developed by the spreadsheets NIP.

This commit is contained in:
Vitor Pamplona 2024-09-24 09:37:53 -04:00
parent 9904494293
commit 6fa638eabc

124
82.md
View File

@ -2,18 +2,17 @@ NIP-82
======
Medical Data
-----------------
------------
`draft` `optional` `author:vitorpamplona`
`draft` `optional`
This NIP defines `kind:32225` (a parameterized replaceable event according to NIP-33) to carry secret-encrypted medical information in the form of individual FHIR Resources and a `kind:32226` to allow authorized users to share the secret to decrypt the information forward.
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
## 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.
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:
@ -63,59 +62,98 @@ An [immunization](https://www.hl7.org/fhir/immunization.html), for instance, is
}
```
### Inserting FHIR Resources into NOSTR
## Plaintext Event
Medical Data in NOSTR must be encrypted in a way that allows providers and patients to reshare their medical information to other individuals after the event is broadcasted. The consent to access a medical record is established by having access to the individual secret that encrypts each fhir resource.
Event kind `82` accepts a JSON-stringified FHIR Bundle directly on its `.content`.
This NIP uses a **secret-encrypted** parameterized replaceable event to represent each individual FHIR Resource. Encrypted content is placed in the `.content` of these kind-`32225` events. Similar to NIP-04, `.content` MUST be equal to the base64-encoded, aes-256-cbc encrypted JSON-serialized representation of the Resource using a newly created 64-byte secret.
The `d` tag must be equal to the FHIR resource id and the author of the event must be the data holder, the controller of the uniqueness of resource ids (generally a hospital or an EHR system). The provider that is filing the resource, should be included as an `e` tag. The subject of the resource (generally the patient), must be tagged with another `e` tag. Markers must be equal to the field names in the resource.
A 5th parameter per `e` tag contains the secret to decode the `.content`. Each secret is encrypted to the pubkey of each `e` hex. By default, a resource is accessible to all cited entities in the FHIR resource. From the sender's perspective, the secret is stored in an `e` tag with the sender's public key. Secrets are thus not saved anywhere else but in the message itself.
The example below contains a Vision Prescription.
```json
{
"kind": 32225,
"created_at": 1675642635,
"content": "secret-encrypted-single-fhir-resource(Resource)",
"id": "6139d0c98dc9e24f9359faa6bf7ec34e2e793d51a1d69909e4078e29be8c7445",
"kind": 82,
"pubkey": "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
"created_at": 1725747978,
"tags": [
["d", "resource_id"]
["e", "acfd0487aea5a7450b3481c60b6e4f87b3e392b11f5d4f28321cedd09303a748", "wss://relay.example.com", "author", "receivers-pubkey-encrypted-secret"]
["e", "acfd0487aea5a7450b3481c60b6e4f87b3e392b11f5d4f28321cedd09303a748", "wss://relay.example.com", "subject", "receivers-pubkey-encrypted-secret"],
["e", "b11f5d4f28321cedd09303a748acfd0487aea5a7450b3481c60b6e4f87b3e392", "wss://relay.example.com", "practitioner", "receivers-pubkey-encrypted-secret"],
...
[ "p", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c" ]
],
"pubkey": "...",
"id": "..."
"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",
}
```
The collection of such event kinds will assemble a medical record.
Kind `82`s SHOULD be NIP-59 gift wrapped using `kind:1059` to each receiving user for the best privacy.
### Additional Secret-Sharing Event
Kind `82` can also be included inside [NIP-17](17.md) DMs via (embed)[https://github.com/nostr-protocol/nips/pull/1078] events.
If the patient wishes to share it's record with another Nostr user (say another provider or a family member), the client must assemble a Kind `32226` event. This Secret-Sharing nostr event allows any secret-holder to share it forward.
## Encrypted Wraps with Consent Management
The `.content` of the secret sharing event is the encrypted secret using the public key of the receiver. The `a` references the resource the receiver is obtaining access to and the `e` tag contains the receiver's public key.
Event `kind:32225` carries **secret-encrypted** medical information (kind:`82`) in it's `.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 amoung participants via encrypted `key`-tags to each receiver.
```js
val sign = nostr.generateKeyPair()
val view = nostr.generateKeyPair()
```json
{
"kind": 32226,
"created_at": 1675642635,
"content": "receivers-pubkey-encrypted-secret",
"pubkey": sign.publicKey
"kind": 32225,
"tags": [
["a", "32225:...:resource_id"]
["e", "b11f5d4f28321cedd09303a748acfd0487aea5a7450b3481c60b6e4f87b3e392", "wss://relay.example.com", "practitioner"],
...
["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
],
"pubkey": "...",
"id": "..."
"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 key 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. In order to share with a new person, that new person must have a Nostr key and receive the event via relays.
Relays don't have access to private key 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 accoding to its authorized activitiy.
@ -123,14 +161,4 @@ It is expected that Health Information will be kept in specialized relays due to
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 author to periodically rotate these secrets while maintaining access to the relevant people. Receivers of kind `32226` must receive a new event with the new secret to keep access to a record.
### Linking
The FHIR Resource may be linked to using the NIP-19 `naddr` code along with the `"a"` tag (see NIP-33 and NIP-19).
## Example Event
```json
TBD
```
It is expected that some jurisdictions require author to periodically rotate these secrets while maintaining access to the relevant people.