Skip to main content

Identity Connection (Kind 35521)

A Kind 35521 is a verifiable Nostr event published by an end-user to announce that their Nostr public key is linked to a Legacy Identity Provider (LIDP) account, such as a Discord or X account.

Crucially, because an end-user could lie about owning an account, Kind 35521 alone is insufficient. It must encapsulate cryptographic proof signed by a trusted third party—an Identity Authority (IA).

Event Structure

{
"kind": 35521,
"content": "{\"privacy\": \"public\"}",
"tags": [
["d", "<identifier_hash>"],
["lidp", "<lidp_name>"],
["s", "<embedded_kind_35522_json>", "<ia_pubkey>", "<ia_relay_hint>"]
],
"pubkey": "<user_pubkey>",
"created_at": 1709424015,
"id": "...",
"sig": "..."
}

Required Tags

TagFormatDescription
d["d", "<sh256_hash>"]The ConnectionKey. Generated via SHA256(lidp_name + ":" + identifier) (e.g., discord:12345). This allows querying without exposing the raw identifier.
lidp["lidp", "<string>"]The name of the Legacy Identity Provider (e.g., discord, email, x).
s["s", "<json>", "<pubkey>", "<url>"]The embedded Kind 35522 evidence event signed by the IA. See below.

The s Tag and Multi-IA Stacking

The s tag is the heart of Zapf's identity model.

  • Index 1: The raw, stringified JSON of the IA's Kind 35522 Attestation.
  • Index 2: The pubkey of the IA that signed the attestation.
  • Index 3: A relay hint where clients can fetch the original Kind 35522 for an online Deep Check.

Stacking: A user can include multiple s tags in their Kind 35521 event. If a user proves their Discord account to three different IAs (zapf.app, loki.ltd, community.ia), they can embed all three attestations. This means a client only needs to trust one of those three IAs to verify the connection.

Content Privacy Modes

By default, the d tag only broadcasts a hash. If a user wants to be searchable by their raw identifier (e.g., finding @elonmusk on X), they store the raw string in the content field.

The content field is a JSON string with a privacy key:

ModeContent FieldResult
Public{"privacy": "public", "value": "elonmusk"}The client can read the value, hash it, and verify it matches the d tag. The user is searchable globally.
Private{"privacy": "private"}The raw value is entirely omitted. The Connection Key is strictly a hash. Users can only zap this person if they already know their exact email/phone number.