{"id":"6cb84a492bc827ae3937c99ed656a63a24614f350fa5e1883c264c32ddbc08bd","pubkey":"da19f1cd34beca44be74da4b306d9d1dd86b6343cef94ce22c49c6f59816e5bd","created_at":1774618150,"kind":30817,"tags":[["title","NIP-LOCATION: Privacy-Preserving Location Discovery"],["k","20500"],["k","20501"],["d","nip-location"]],"content":"NIP-LOCATION\n============\n\nPrivacy-Preserving Location Discovery\n--------------------------------------\n\n`draft` `optional`\n\nTwo ephemeral event kinds for privacy-preserving geospatial presence and location sharing on Nostr. Publishers broadcast coarse-grained presence beacons for public discovery, then share precise coordinates only with specific recipients via NIP-44 encryption after consent is established. The same primitives support both real-time mobile tracking and static venue privacy for addressable events.\n\n> **Standalone.** This NIP works independently on any Nostr application.\n\n## Motivation\n\nLocation-aware applications on Nostr — delivery, field services, event coordination, fleet tracking, calendar events, marketplace listings — need a standard way to publish presence and share precise coordinates without leaking location data to the network at large. Existing approaches either expose exact coordinates publicly or require centralized location servers.\n\nThis NIP defines a two-tier model: coarse public discovery via geohash-indexed beacons, and precise private sharing via NIP-44 encrypted updates. The progressive reveal pattern ensures that location precision never increases without the publisher's consent.\n\n## Relationship to Existing NIPs\n\n- **NIP-38 (User Statuses):** User statuses carry text like \"working\" or \"at the gym\" with no geospatial semantics, no geohash indexing, no encryption model, and no progressive precision reveal. Presence beacons require geohash `g` tags for relay-side spatial filtering, which NIP-38 does not define.\n- **NIP-52 (Calendar Events):** Calendar events represent confirmed, scheduled happenings with static locations. Presence beacons represent real-time, ephemeral location signals from mobile publishers. The use cases do not overlap.\n- **\"Why not `g` tags on any event kind?\":** A dedicated presence kind (20500) enables relay-side subscriptions by geohash without downloading all events of every kind in an area. Clients subscribing to `{kinds:[20500], #g:[\"gcpu\"]}` receive only presence signals; without a dedicated kind, they would receive every geotagged note, calendar event, and marketplace listing in the cell.\n\n## Kinds\n\n| kind  | description                                              |\n| ----- | -------------------------------------------------------- |\n| 20500 | Presence Beacon — coarse geospatial presence (ephemeral) |\n| 20501 | Location Update — precise coordinates, encrypted (ephemeral) |\n\n## Key Concepts\n\n### Coordinate Reference System\n\nAll latitude/longitude values in this NIP use the **WGS 84 (EPSG:4326)** coordinate reference system — the standard used by GPS receivers and most mapping services. Geohash encoding (used in `g` tags) is defined over WGS 84 coordinates.\n\n### Geohash Precision Tiers\n\n| Precision | Approximate Cell Size | Recommended Use                                         |\n| --------- | --------------------- | ------------------------------------------------------- |\n| 3         | ~156 km x 156 km     | Regional fallback, rural areas                          |\n| 4         | ~39 km x 39 km       | City-level presence, default for beacons                |\n| 5         | ~5 km x 5 km         | Neighbourhood-level, service areas                      |\n| 6         | ~1.2 km x 1.2 km     | Post-consent only (NIP-44 encrypted)                    |\n| 7+        | <300 m                | Active sharing only (NIP-44 encrypted, never public)    |\n\n### Nine-Cell Subscription\n\nTo avoid edge effects when a publisher is near a geohash cell boundary, subscribers MUST subscribe to the target cell plus its eight neighbours:\n\n```\n+-------+-------+-------+\n|  NW   |   N   |  NE   |\n+-------+-------+-------+\n|   W   |   *   |   E   |\n+-------+-------+-------+\n|  SW   |   S   |  SE   |\n+-------+-------+-------+\n\n* = subscriber's cell\n```\n\nThis ensures that a publisher positioned at a cell edge is visible to subscribers on either side of the boundary.\n\n### Progressive Reveal\n\nLocation precision increases as trust increases:\n\n1. **Public discovery:** Geohash precision 4-5 only (~5-40 km). Anyone can see approximate area.\n2. **Post-consent:** Geohash precision 6+ (~1.2 km). Encrypted to specific recipients.\n3. **Active sharing:** Exact coordinates via `kind:20501`. NIP-44 encrypted to named recipients.\n\nThe following diagram illustrates the progressive reveal flow from public discovery through consent to active sharing:\n\n\n![Progressive Reveal Flow](https://raw.githubusercontent.com/forgesworn/nip-drafts/main/images/location-1.png)\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant S as Subscriber\n    participant R as Relay\n    participant P as Publisher\n\n    rect rgb(27, 45, 61)\n        Note over S,P: Phase 1 — Public Discovery (precision 4-5, ~5-40 km)\n        P->>R: kind:20500 Presence Beacon<br/>g=gcpu, status=available\n        P->>R: kind:20500 Beacon (30s interval)\n        S->>R: REQ {kinds:[20500], #g:[9-cell neighbours]}\n        R-->>S: Matching beacons\n        Note over S: Sees approximate area only\n    end\n\n    rect rgb(45, 45, 27)\n        Note over S,P: Phase 2 — Consent Established (application-specific)\n        S->>P: Consent exchange (out of band)\n        P-->>S: Consent acknowledged\n    end\n\n    rect rgb(27, 61, 45)\n        Note over S,P: Phase 3 — Active Sharing (exact coordinates, NIP-44 encrypted)\n        P->>R: kind:20501 Location Update<br/>NIP-44 encrypted to Subscriber<br/>consent=explicit\n        R-->>S: Encrypted location update\n        Note over S: Decrypts: lat, lon,<br/>accuracy, bearing, speed\n        P->>R: kind:20501 (every few seconds)\n        R-->>S: Encrypted location update\n    end\n\n    rect rgb(61, 27, 27)\n        Note over S,P: Termination — context ends\n        Note over P: Stops publishing\n        Note over R: Ephemeral events expire<br/>via NIP-40 expiration tags\n    end\n```\n\n## Presence Beacon (`kind:20500`)\n\nEphemeral event declaring presence in a geographic area. Publishers SHOULD publish every 30 seconds whilst active, with a 30-minute NIP-40 `expiration` tag as a safety net.\n\n```json\n{\n    \"kind\": 20500,\n    \"pubkey\": \"<publisher-hex-pubkey>\",\n    \"created_at\": 1698765432,\n    \"tags\": [\n        [\"g\", \"gcpuu\"],\n        [\"g\", \"gcpu\"],\n        [\"status\", \"available\"],\n        [\"alt\", \"Presence beacon: available in gcpuu\"],\n        [\"expiration\", \"1698767232\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-byte-hex>\",\n    \"sig\": \"<64-byte-hex>\"\n}\n```\n\n* `g` (REQUIRED, one or more): Geohash at precision 4-5. Multiple tags MAY be used to publish at multiple precision levels simultaneously.\n* `status` (REQUIRED): One of `available`, `busy`, `offline`.\n* `expiration` (REQUIRED): NIP-40 expiration timestamp. MUST be set (30-minute maximum recommended). Safety net — if the publisher stops broadcasting, stale beacons expire automatically.\n\nOptional tags that publishers MAY include for richer discovery:\n\n* `t`: Freeform category tag (e.g. `plumber`, `courier`, `photographer`). Enables category-filtered discovery.\n\n### Subscribing to Beacons\n\nClients subscribing to presence beacons use a REQ filter like:\n\n```json\n{\n    \"kinds\": [20500],\n    \"#g\": [\"gcpuuz\", \"gcpuuy\", \"gcpuux\", \"gcpuuv\", \"gcpuuw\", \"gcpvn0\", \"gcpvn1\", \"gcpvn2\", \"gcpvn3\"]\n}\n```\n\nThe nine geohash values correspond to the subscriber's cell and its eight neighbours.\n\n## Location Update (`kind:20501`)\n\nEphemeral event sharing precise coordinates with specific recipients. ALL location data is NIP-44 encrypted — relays can see that a location update exists but MUST NOT be able to read the coordinates.\n\n```json\n{\n    \"kind\": 20501,\n    \"pubkey\": \"<publisher-hex-pubkey>\",\n    \"created_at\": 1698765432,\n    \"tags\": [\n        [\"p\", \"<recipient-pubkey>\"],\n        [\"g\", \"gcpuu\"],\n        [\"context\", \"<shared-context-id>\"],\n        [\"consent\", \"explicit\"],\n        [\"alt\", \"Encrypted location update for shared context\"],\n        [\"expiration\", \"1698765462\"]\n    ],\n    \"content\": \"<NIP-44 encrypted JSON>\",\n    \"id\": \"<32-byte-hex>\",\n    \"sig\": \"<64-byte-hex>\"\n}\n```\n\n* `p` (REQUIRED, one or more): Pubkeys of recipients. The content is NIP-44 encrypted pairwise to the recipient. When multiple `p` tags are present, the publisher MUST fan out separate events — one per recipient — each with content encrypted pairwise to that recipient's pubkey.\n* `g` (RECOMMENDED): Coarse geohash (precision 4-5) derived from the encrypted coordinates. Enables relay-side geographic filtering without revealing exact location. When GPS is unavailable, this tag MAY be omitted if a `location_status: gps_lost` tag is present instead.\n* `context` (RECOMMENDED): An identifier scoping this location stream to a shared context (e.g. a task, order, event, or session). Enables filtering.\n* `consent` (REQUIRED): Declares the basis for location sharing. MUST be one of:\n    * `explicit` — Publisher has explicitly consented to share location.\n    * `contextual` — Location sharing is a condition of the shared context.\n    * `policy` — Location sharing is required by a platform or service policy.\n* `expiration` (RECOMMENDED): NIP-40 expiration timestamp.\n\n### Encrypted Content Fields\n\nThe `content` field is a JSON object NIP-44 encrypted pairwise to the recipient (one event per recipient when multiple `p` tags are used):\n\n| Field              | Type             | Required | Description                                         |\n| ------------------ | ---------------- | -------- | --------------------------------------------------- |\n| `lat`              | Decimal degrees  | Yes      | Latitude                                            |\n| `lon`              | Decimal degrees  | Yes      | Longitude                                           |\n| `accuracy_meters`  | Meters           | Yes      | GPS accuracy radius                                 |\n| `bearing`          | Degrees (0-360)  | No       | Direction of travel (0 = north, 90 = east)          |\n| `speed_kmh`        | Km/h             | No       | Current speed                                       |\n| `altitude_meters`  | Meters           | No       | Altitude above sea level                            |\n| `timestamp`        | Unix timestamp   | Yes      | Time of the GPS fix                                 |\n\nExample decrypted content:\n\n```json\n{\n    \"lat\": 51.4950,\n    \"lon\": -0.1100,\n    \"accuracy_meters\": 8,\n    \"bearing\": 355,\n    \"speed_kmh\": 32,\n    \"altitude_meters\": 45,\n    \"timestamp\": 1698765432\n}\n```\n\n## Protocol Flow\n\n1. **Discovery phase:** Publisher broadcasts `kind:20500` beacons at coarse geohash precision (4-5). Subscribers find nearby publishers via nine-cell subscription.\n2. **Consent phase:** Parties establish a shared context and agree to location sharing (out of band for this NIP — consent mechanisms are application-specific).\n3. **Active sharing phase:** Publisher sends `kind:20501` updates with NIP-44 encrypted precise coordinates to specific recipients. Updates SHOULD be sent every few seconds during active sharing.\n4. **Termination:** When the shared context ends, ALL location sharing ceases immediately. Since events are ephemeral, no cleanup is required — stale events expire via `expiration` tags.\n\n## Application Policy Boundary\n\nThis NIP defines location event structure and privacy transport rules. It does not prescribe legal/compliance policy or product-specific access policy.\n\n- **No auto-reveal requirement:** The NIP does not require automatic location disclosure after RSVP, booking, or acceptance.\n- **Organizer-mediated access supported:** Publishers can share `kind:20501` only with explicit `p` recipients approved by organiser or platform policy.\n- **Venue policy is standardised but optional:** The `venue-visibility` tag (`open`, `private`, `semi-private`) standardises how applications communicate location privacy policy. Publishers are not required to use it, but when present, clients SHOULD respect it. See [Static Location Privacy](#static-location-privacy).\n- **Jurisdictional obligations are app-level:** GDPR/PII handling, payment processor scope, and local legal requirements are implementation responsibilities.\n\n## Static Location Privacy\n\nThe protocol flow above describes **real-time** location sharing — mobile participants broadcasting presence beacons and streaming coordinates. Many applications also need **static** location privacy for fixed venues: a home address, a private studio, a community centre that should only be discoverable to approved attendees.\n\nStatic location privacy builds on the same two kinds (`kind:20500` and `kind:20501`) but applies them differently.\n\n### Geohash Tags on Non-Ephemeral Events\n\nAddressable events with a physical location (e.g. NIP-52 calendar events, NIP-99 classified listings, marketplace offers) MAY include `g` tags for relay-side geographic filtering. This is a valid discovery mechanism alongside `kind:20500` beacons — the geohash tag enables `#g` subscription filters without requiring a separate ephemeral beacon.\n\n```json\n{\n    \"kind\": 31923,\n    \"tags\": [\n        [\"d\", \"jazz-night-march\"],\n        [\"g\", \"gcpuu\"],\n        [\"location\", \"Bristol Community Center\"],\n        [\"alt\", \"Calendar event: Jazz Night at Bristol Community Center\"]\n    ],\n    \"content\": \"...\",\n    \"id\": \"<32-byte-hex>\",\n    \"sig\": \"<64-byte-hex>\"\n}\n```\n\nThe `g` tag on a non-ephemeral event follows the same precision conventions as beacons: precision 4-5 for public discovery (~5-40 km), never higher than precision 5 on public events.\n\n### Venue-Visibility Tag\n\nThe `venue-visibility` tag is OPTIONAL and application-specific. It is not required for the core presence beacon and location update protocol.\n\nPublishers MAY include a `venue-visibility` tag on any addressable event that has a physical location. This tag declares the location privacy policy for the event:\n\n| Value | Meaning | `g` tag precision | Precise location |\n|-------|---------|-------------------|-----------------|\n| `open` | Full address is public | 5 (~5 km) | Published in the event (e.g. `geo` tag or `location` tag with full address) |\n| `private` | Approximate area only; precise location granted individually | 4 (~39 km) | NIP-44 self-encrypted in event content; organiser grants per-recipient via `kind:20501` |\n| `semi-private` | Approximate area public; precise location auto-granted to group members or confirmed attendees | 5 (~5 km) | NIP-44 self-encrypted in event content; auto-granted on booking confirmation or group membership |\n\nExample:\n\n```json\n{\n    \"kind\": 31923,\n    \"tags\": [\n        [\"d\", \"home-education-meetup\"],\n        [\"g\", \"gcpu\"],\n        [\"venue-visibility\", \"private\"],\n        [\"alt\", \"Private venue event: home education meetup\"]\n    ],\n    \"content\": \"<NIP-44 self-encrypted JSON containing precise location>\",\n    \"id\": \"<32-byte-hex>\",\n    \"sig\": \"<64-byte-hex>\"\n}\n```\n\nWhen `venue-visibility` is `private` or `semi-private`:\n- The `g` tag SHOULD use a coarser precision (4 for private, 5 for semi-private) to avoid narrowing the area too much.\n- The precise location (latitude, longitude, full address) SHOULD be NIP-44 encrypted to the publisher's own pubkey and stored in the event `content` field.\n- The publisher distributes precise location to approved recipients via `kind:20501` location updates (see below).\n\nWhen `venue-visibility` is absent, clients SHOULD treat the event as `open` if a precise location is visible, or infer the policy from available tags.\n\n### Self-Encrypt-Then-Grant Pattern\n\nFor static venues, the publisher stores the precise location encrypted to their own pubkey in the addressable event. When a recipient is approved, the publisher decrypts the location locally and re-publishes it as a `kind:20501` location update encrypted to the recipient:\n\n1. **Publisher creates the event** with precise location NIP-44 self-encrypted in `content`.\n2. **Recipient requests access** (application-specific — e.g. RSVP, booking request, group membership).\n3. **Publisher approves** and publishes `kind:20501`:\n   - `p` tag: the approved recipient's pubkey (or multiple `p` tags for bulk grants)\n   - `context` tag: the addressable event's `a`-tag reference (e.g. `31923:<pubkey>:<d-tag>`)\n   - `consent`: `explicit`\n   - `content`: NIP-44 encrypted JSON with `lat`, `lon`, `accuracy_meters`, `timestamp`\n   - `expiration`: event end time plus a reasonable buffer (e.g. 2 hours)\n4. **Recipient decrypts** and caches the precise location locally.\n5. **Expiry:** The ephemeral `kind:20501` expires via NIP-40. The recipient's local cache persists (they already know the location), but no new grants are issued.\n\n### Auto-Grant Triggers\n\nApplications MAY implement automatic `kind:20501` grants based on context:\n\n- **Group membership:** If the event is linked to a group and the recipient is a verified group member, the publisher's client auto-publishes the grant on RSVP or booking confirmation.\n- **Booking confirmation:** If the event uses a booking system, the publisher's client auto-publishes the grant when the booking is confirmed.\n- **Coordinator mediation:** If a coordinator facilitates the interaction, the coordinator MAY trigger the grant on behalf of the publisher after verifying the recipient.\n\nAuto-grant logic is application-defined. This NIP provides the transport (`kind:20501`); the trigger policy is above the protocol layer.\n\n## Recommended Geohash Precision Tiers\n\nThe three-tier privacy model maps to specific geohash precisions:\n\n| Tier | Precision | Approximate Area | Use Case |\n|------|-----------|-----------------|----------|\n| **Coarse** (public relay) | 4–5 characters | ~20km × 20km to ~5km × 5km | Discovery — \"nearby participants\" |\n| **Fine** (matched parties) | 7–8 characters | ~150m × 150m to ~40m × 40m | Coordination — \"heading to your area\" |\n| **Exact** (NIP-44 encrypted) | Full coordinates | Point location | Meetup — \"I'm at the front door\" |\n\n**Recommendations by context:**\n- **Urban areas:** Coarse=5, Fine=7 (denser population needs tighter coarse bounds)\n- **Rural areas:** Coarse=4, Fine=6 (wider areas, fewer participants)\n- **High-security:** Coarse=4, Fine=8, Exact only after mutual NIP-44 key exchange\n\n## Example Applications\n\n### Social Meetups\nUsers share coarse location on a relay to signal \"I'm in this neighborhood.\" Matched friends receive fine location. Exact coordinates shared only when ready to meet.\n\n### Emergency Beacons\nA distress signal broadcasts coarse location publicly for any nearby responder. Fine location revealed to first responders who acknowledge. Exact coordinates sent encrypted to emergency contacts.\n\n### Delivery Tracking\nCourier shares fine-tier location updates with the recipient during active delivery. Coarse location visible on a public tracking relay. Exact drop-off coordinates shared at final mile.\n\n### Fleet Management\nVehicles broadcast coarse location to a fleet relay. Dispatchers receive fine location. Exact coordinates logged for compliance but never broadcast.\n\n## Security Considerations\n\n* **Precision never increases without consent.** Public beacons (precision 4-5) reveal only a ~5-40 km area. Exact coordinates are only shared via NIP-44 encrypted `kind:20501` events after explicit consent.\n* **Ephemeral events.** Both kinds are in the ephemeral range (20000-29999). Relays MUST NOT persist them. This prevents location history accumulation on relays.\n* **Expiration tags.** The `expiration` tag ensures stale beacons do not persist if a publisher goes offline without sending an `offline` status.\n* **Encrypted content.** `kind:20501` content is NIP-44 encrypted. Relays see that a location update exists but cannot read coordinates.\n* **Consent declaration.** The `consent` tag makes the basis for location sharing explicit and auditable.\n* **No location history.** Ephemeral events are not persisted. Implementations SHOULD NOT build location history from ephemeral events beyond what is needed for the active context.\n* **Geohash leakage.** Even coarse geohashes reveal approximate location. Publishers who require anonymity SHOULD NOT publish `kind:20500` beacons at all.\n* **Replay attacks.** Consumers SHOULD validate `created_at` timestamps and discard events older than a reasonable threshold (e.g. 5 minutes for beacons, 30 seconds for location updates).\n\n## Test Vectors\n\n### Kind 20500 — Presence Beacon\n\n```json\n{\n  \"kind\": 20500,\n  \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n  \"created_at\": 1709740800,\n  \"tags\": [\n    [\"g\", \"gcpuu\"],\n    [\"g\", \"gcpu\"],\n    [\"status\", \"available\"],\n    [\"alt\", \"Presence beacon: available in gcpuu\"],\n    [\"expiration\", \"1709742600\"]\n  ],\n  \"content\": \"\",\n  \"id\": \"<32-byte-hex>\",\n  \"sig\": \"<64-byte-hex>\"\n}\n```\n\n### Kind 20501 — Location Update\n\n```json\n{\n  \"kind\": 20501,\n  \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n  \"created_at\": 1709740800,\n  \"tags\": [\n    [\"p\", \"b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3\"],\n    [\"g\", \"gcpuu\"],\n    [\"context\", \"delivery_order_42\"],\n    [\"consent\", \"explicit\"],\n    [\"alt\", \"Encrypted location update for delivery order 42\"],\n    [\"expiration\", \"1709740830\"]\n  ],\n  \"content\": \"<NIP-44 encrypted JSON>\",\n  \"id\": \"<32-byte-hex>\",\n  \"sig\": \"<64-byte-hex>\"\n}\n```\n\nDecrypted `content` for the above:\n\n```json\n{\n  \"lat\": 51.495,\n  \"lon\": -0.11,\n  \"accuracy_meters\": 8,\n  \"bearing\": 355,\n  \"speed_kmh\": 32,\n  \"altitude_meters\": 45,\n  \"timestamp\": 1709740800\n}\n```\n\n## Dependencies\n\n* [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md): Basic protocol flow, event format\n* [NIP-40](https://github.com/nostr-protocol/nips/blob/master/40.md): Expiration timestamps\n* [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md): Versioned encrypted payloads\n\n## Reference Implementation\n\nNo public reference implementation exists yet. Implementors SHOULD refer to the kind definitions above and the [geohash algorithm](https://en.wikipedia.org/wiki/Geohash) for computing cell neighbours.\n\nA minimal implementation requires:\n\n1. A geohash library capable of encoding coordinates and computing the eight neighbours of a cell.\n2. A NIP-44 encryption library for encrypting `kind:20501` content.\n3. A Nostr client that supports ephemeral event publishing and subscription filtering on `g` tags.","sig":"807b71899e7f74ecd4f16cca4a988a0c264881e738d95d1289372d469e2b71fbd6da0dd3796db29927cc598e69d51056a12fadab0a01eac71915e6fda5dbec47"}