From edbfe8fb0c6094ad09a92e3af095fbec6d7a671e Mon Sep 17 00:00:00 2001 From: James Fairweather Date: Fri, 10 Jul 2026 15:15:37 -0400 Subject: [PATCH] NIP-DA: Permissioned Private Data Sharing (Scoped Data Grants) --- DA.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 5 ++ 2 files changed, 179 insertions(+) create mode 100644 DA.md diff --git a/DA.md b/DA.md new file mode 100644 index 0000000000..7567625f38 --- /dev/null +++ b/DA.md @@ -0,0 +1,174 @@ +# NIP-DA + +## Permissioned Private Data Sharing (Scoped Data Grants) + +`draft` `optional` + +This NIP defines a permission-based model for sharing private, structured personal data (e.g. contact details) between nostr keyholders. Each user maintains one or more encrypted, authoritative **Scoped Data Sets** on relays. Access is granted per-recipient by privately delivering a **Data Grant** containing the symmetric key for a scope. Relays store only ciphertext and never learn the contents of a data set or the identities of grantees. + +The design goals are: + +- **Self-sovereign data**: the publisher holds the only authoritative copy; recipients dereference it rather than storing snapshots. +- **Live updates**: when the publisher updates a data set, all current grantees see the new version on next fetch, with no re-sharing step. +- **Scoped disclosure**: different recipients can be granted different subsets (scopes) of the publisher's data. +- **Private grant graph**: relays and third parties cannot determine who has granted access to whom. +- **Revocability of future access**: a publisher can rotate a scope key to cut off future updates to any grantee (historical plaintext already decrypted by a grantee is, unavoidably, retained by them). + +This NIP builds on [NIP-44](44.md) (encrypted payloads) and [NIP-59](59.md) (gift wrap). It defines no new relay behavior; conforming relays require only standard NIP-01 support for addressable events. + +## Definitions + +- **Publisher**: the keyholder who owns and maintains a data set. +- **Grantee**: a keyholder who has been granted access to one or more of the publisher's scopes. +- **Scope**: a named subset of the publisher's data (e.g. `basic`, `personal`, `business`), encrypted under its own key. +- **Scope key**: a random 32-byte symmetric key under which a scope's data set is encrypted. +- **Grant**: the private delivery of a scope key (plus a pointer to the data set) from publisher to grantee. + +## Event Kinds + +| kind | description | encryption | +| ------- | ------------------------------------ | ----------------------------------- | +| `30440` | Scoped Data Set (addressable) | symmetric, under scope key | +| `440` | Data Grant (unsigned rumor) | NIP-59 seal + gift wrap | +| `441` | Grant Revocation notice (rumor) | NIP-59 seal + gift wrap (optional) | +| `10440` | Grant Index (replaceable, self-use) | NIP-44 to self | + +Kind numbers are placeholders pending assignment. + +## Scoped Data Set (`kind:30440`) + +An addressable event holding one scope's data as ciphertext. The `d` tag identifies the scope. + +```json +{ + "kind": 30440, + "pubkey": "", + "tags": [ + ["d", ""], + ["v", ""] + ], + "content": "", + ... +} +``` + +- `d`: an opaque scope identifier. Publishers SHOULD use short opaque strings (e.g. random 8-char ids) rather than semantic names like `family`, since `d` tags are visible to relays and semantic names leak information about the publisher's disclosure structure. The human-readable scope name belongs inside the encrypted payload or the publisher's Grant Index. +- `v`: an integer, incremented each time the scope key is rotated. This lets grantees detect that their key is stale without attempting decryption. + +### Payload encryption + +The `content` is produced using the NIP-44 v2 payload format, with the 32-byte scope key used directly as the `conversation_key` (no ECDH step). This reuses NIP-44's authenticated encryption, versioning, and padding scheme — the padding is important, as it reduces leakage of data-set size to relays. + +### Payload schema + +The decrypted payload is a JSON object: + +```json +{ + "name": "Personal", + "updated_at": 1751904000, + "fields": { + "display_name": "James", + "tel": [{"value": "+1...", "label": "mobile"}], + "email": [{"value": "james@example.com", "label": "personal"}], + "adr": [{"value": "...", "label": "home"}], + "note": "..." + } +} +``` + +Field names SHOULD follow vCard 4.0 (RFC 6350) property names in lowercase where an equivalent exists (`tel`, `email`, `adr`, `url`, `bday`, `org`, `title`), so that clients can interoperate with existing contact ecosystems. Arbitrary additional fields MAY be included; clients MUST ignore fields they do not understand. + +As with all addressable events, publishing a new `kind:30440` with the same `d` tag replaces the previous one. This is the mechanism that makes grants "live": grantees always dereference the current event. + +## Data Grant (`kind:440`) + +A grant delivers a scope key to a grantee. It MUST be an **unsigned rumor**, sealed (`kind:13`) and gift-wrapped (`kind:1059`) to the grantee exactly as specified in NIP-59, so that relays observe only an ephemeral pubkey delivering an opaque payload to the grantee. Publishers SHOULD also gift-wrap a copy to themselves for recoverability, following the NIP-17 convention. + +The rumor: + +```json +{ + "kind": 440, + "pubkey": "", + "created_at": 1751904000, + "tags": [ + ["a", "30440::", ""], + ["v", ""], + ["expiration", ""] + ], + "content": "{\"scope_key\":\"\",\"scope_name\":\"Personal\"}" +} +``` + +- `a`: address of the data set this key decrypts. One or more relay hints SHOULD be included, since the grantee may share no relays with the publisher. +- `v`: the key generation this grant corresponds to. +- `expiration` (optional): a NIP-40 style timestamp after which the grantee SHOULD treat the grant as lapsed and clients SHOULD stop displaying the data. This is advisory — it is enforced by honest clients, not cryptography. +- `content`: JSON carrying the scope key and optional human-readable metadata. + +Because the rumor is unsigned, a leaked grant is deniable; the grantee nevertheless authenticates it via the seal, per NIP-59. + +### Grant requests + +A prospective contact MAY request access by sending an ordinary NIP-17 direct message, or clients MAY implement a dedicated request flow out of band (QR code, link). This NIP deliberately does not standardize a request event: the grant decision is a human decision, and existing DM rails are sufficient to carry it. + +## Updates, rotation, and revocation + +**Updating data.** The publisher re-encrypts the payload under the existing scope key and republishes `kind:30440`. No per-grantee action is required. Grantees SHOULD refetch data sets they hold grants for opportunistically (on app open, before displaying a contact, or on a periodic schedule). + +**Revoking a grantee.** The publisher: + +1. Generates a new scope key; increments `v`. +2. Republishes the `kind:30440` event encrypted under the new key. +3. Issues fresh `kind:440` grants (new `v`) to all *remaining* grantees. +4. MAY send a `kind:441` revocation notice to the revoked party (gift-wrapped), containing the `a` tag of the affected scope, so their client can gracefully mark the contact data as no longer maintained. Publishers who prefer silent revocation simply skip this. + +A revoked grantee retains any plaintext previously decrypted but can no longer read updates. Clients holding a grant whose `v` no longer matches the current data set event, and which have not received a fresh grant, SHOULD display the last-known data as stale/unmaintained rather than deleting it. + +Key rotation cost is O(remaining grantees) per rotation. Publishers with large grantee sets SHOULD structure scopes so that broad, low-sensitivity data (rarely revoked) is separated from narrow, high-sensitivity data (small grantee count, cheap to rotate). + +**Deleting a scope.** Replacement of an addressable event destroys the prior ciphertext on conforming relays, so deletion is a special case of rotation: the publisher SHOULD publish a final `kind:30440` replacement (a *tombstone*) with an empty payload, an incremented `v`, and a freshly generated key that is granted to no one, and MAY additionally publish a [NIP-09](09.md) deletion request (`kind:5` with the `a` tag of the scope) asking relays to drop the tombstone as well. Grantees observe generation supersession and treat the scope exactly as a revocation; previously decrypted plaintext is unaffected (see Security 2). The publisher then removes the scope from their Grant Index. + +## Grant Index (`kind:10440`) + +To make grants recoverable across clients with only the user's private key, both publishers and grantees SHOULD maintain a replaceable Grant Index event whose `content` is NIP-44 encrypted to their own key (conversation key derived from their own keypair, as in NIP-51 private items): + +```json +{ + "issued": [ + {"scope": "", "scope_name": "Personal", "v": 3, + "key": "", "grantees": ["", "..."]} + ], + "received": [ + {"a": "30440::", "v": 2, "key": "", + "petname": "alice", "relays": ["wss://..."]} + ] +} +``` + +For the publisher, `issued` is the authoritative record needed to perform rotations. For the grantee, `received` is effectively the private address book: a list of dereferenceable, self-updating contact cards. This event contains all key material and MUST never be published unencrypted. + +## Interaction with existing NIPs + +- **NIP-02**: the public follow list and this NIP's private grant layer are independent and complementary. Following someone does not imply a grant in either direction. +- **NIP-51**: a grantee MAY additionally organize granted contacts using private list items; the Grant Index is distinct because it must carry key material. +- **NIP-65**: grantees SHOULD use the publisher's relay list metadata, plus grant relay hints, to locate `kind:30440` events. +- **NIP-05**: publishers MAY include their NIP-05 identifier inside a scope payload; it plays no role in the grant mechanism itself. +- **MLS-based group messaging** (NIP-EE / Marmot) solves a different problem: end-to-end encrypted *conversation streams* with forward secrecy and group evolution. This NIP addresses authoritative, addressable *data records* with live dereference and revocation-by-rotation; the two are orthogonal and complementary. + +## Security and privacy considerations + +1. **No forward secrecy within a scope generation.** Compromise of a scope key exposes the current payload and all payloads published under that generation. Rotation bounds the damage window. High-sensitivity fields warrant their own scope. +2. **Grantees retain decrypted plaintext.** Revocation controls future access only. This matches physical-world disclosure semantics and MUST be communicated honestly by clients ("stop sharing updates," not "un-share"). +3. **Publisher metadata.** The existence, count, `d` tags, update timing, and (padded) size of a publisher's `kind:30440` events are visible to relays. Opaque `d` tags and NIP-44 padding mitigate but do not eliminate this. Publishers MAY additionally publish decoy updates. +4. **Grant graph privacy.** Gift wrapping hides publisher↔grantee links from relays. Traffic analysis by a relay observing both the wrap delivery and a subsequent fetch of a specific `kind:30440` address could correlate; grantees SHOULD fetch via their normal read relays and MAY delay first fetch by a random interval. +5. **Key loss.** Loss of the user's private key forfeits the Grant Index and therefore all issued and received grants. This NIP inherits nostr's key-management model; deployments serious about mainstream contact use cases should pair it with NIP-46 remote signing and/or social recovery schemes, which are out of scope here. +6. **Malicious data set replacement.** Only the publisher can sign a replacement `kind:30440`; grantees MUST verify the event signature and that its pubkey matches the `a` tag before decrypting. +7. **Relay withholding.** A relay can serve a stale `kind:30440` (rollback). The `updated_at` field inside the payload lets clients prefer the newest authenticated payload across multiple relays. + +## Rationale + +- **Symmetric scope keys rather than per-grantee encryption of the payload** make the data set O(1) in grantee count and make updates free, at the cost of rotation-on-revoke. For contact data — low churn in revocations, potentially high churn in field values — this is the right trade. +- **Reusing the NIP-44 payload format with a raw key** avoids introducing a second encryption construction into the ecosystem. +- **Unsigned gift-wrapped grants** follow NIP-17's deniability and metadata-privacy rationale; the grant graph is precisely the information this NIP exists to protect. +- **No new relay features** keeps the barrier to deployment at zero: this NIP is implementable today by clients alone against existing relays. diff --git a/README.md b/README.md index a3e37e99cd..71373e5435 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ NIPs listed here are not a protocol checklist. Nothing forces any software to im - [NIP-C0: Code Snippets](C0.md) - [NIP-C7: Chats](C7.md) - [NIP-CC: Geocaching](CC.md) +- [NIP-DA: Permissioned Private Data Sharing (Scoped Data Grants)](DA.md) - ~~[NIP-EE: E2EE Messaging using MLS Protocol](EE.md) --- **unrecommended**: superseded by the [Marmot Protocol](https://github.com/marmot-protocol/marmot)~~ - [NIP-F4: Podcasts](F4.md) @@ -155,6 +156,8 @@ This table is not exhaustive. For a machine-readable registry of all known event | `62` | Request to Vanish | [62](62.md) | | `64` | Chess (PGN) | [64](64.md) | | `78` | Application-specific Data | [78](78.md) | +| `440` | Data Grant | [DA](DA.md) | +| `441` | Grant Revocation notice | [DA](DA.md) | | `443` | KeyPackage | [Marmot](marmot) | | `444` | Welcome Message | [Marmot](marmot) | | `445` | Group Event | [Marmot](marmot) | @@ -220,6 +223,7 @@ This table is not exhaustive. For a machine-readable registry of all known event | `10051` | KeyPackage Relays List | [Marmot][marmot] | | `10054` | Favorite podcasts list | [51](51.md) | | `10063` | User server list | [B7](B7.md) | +| `10440` | Grant Index | [DA](DA.md) | | `10064` | Authored podcasts list | [51](51.md) | | `10096` | File storage server list | [96](96.md) (deprecated) | | `10154` | Podcast Metadata | [F4](F4.md) | @@ -271,6 +275,7 @@ This table is not exhaustive. For a machine-readable registry of all known event | `30384` | Addressable Trusted Assertion | [85](85.md) | | `30402` | Classified Listing | [99](99.md) | | `30403` | Draft Classified Listing | [99](99.md) | +| `30440` | Scoped Data Set | [DA](DA.md) | | `30617` | Repository announcements | [34](34.md) | | `30618` | Repository state announcements | [34](34.md) | | `30818` | Wiki article | [54](54.md) |