Back to Blog
encryptionnostrsecuritytechnical

NIP-59 Gift Wrap Encryption Explained

December 5, 2024 8 min read | By Redshift Team
Share:

Redshift stores your secrets on public Nostr relays, yet no one can read them except you. This post walks through how NIP-59 Gift Wrap actually works under the hood, why simple encryption isn't enough, and what the three-layer scheme buys you in practice.

The Problem: Metadata Leaks

Encrypting message content is table stakes. The harder problem is metadata. Even with encrypted payloads, a standard Nostr event leaks the sender's public key, the timestamp, and the recipient's public key in the p tag. That's enough for an observer to build a graph of who's talking to whom and when.

For secret management this is worse than it sounds. An attacker watching relay traffic could correlate your identity across projects, figure out when you last rotated credentials, and infer which services you depend on. None of that requires breaking any encryption.

How NIP-59 Gift Wrap Works

NIP-59 addresses metadata leakage with three nested layers. Each layer hides a different piece of information. Let's walk through them bottom-up.

Layer 1: The Rumor (Unsigned Event)

Your actual content goes into an unsigned Nostr event called a "rumor." Because it has no signature, there's no cryptographic link back to any identity.

{
  "kind": 30078,
  "content": "DATABASE_URL=postgres://...",
  "tags": [["d", "my-project|production"]],
  // No "sig" field - unsigned!
}

Layer 2: The Seal (Encrypted Rumor)

The rumor gets NIP-44 encrypted to the recipient and placed in a kind 13 "seal" event. The sender signs the seal with their real key, which proves authorship. But nobody's going to see this signature directly.

{
  "kind": 13,
  "content": "[encrypted rumor]",
  "pubkey": "[sender's real pubkey]",
  "sig": "[sender's signature]"
}

Layer 3: The Gift Wrap (Hidden Sender)

This is where it gets interesting. The seal is encrypted again and wrapped in a kind 1059 event signed by a random, one-time keypair generated just for this message. The timestamp is also randomized within a 48-hour window.

{
  "kind": 1059,
  "content": "[encrypted seal]",
  "pubkey": "[random throwaway pubkey]",
  "created_at": [randomized timestamp],
  "tags": [["p", "[recipient pubkey]"]],
  "sig": "[random key signature]"
}

From the relay's perspective, this event came from some pubkey it's never seen before and will never see again. The real sender is buried two encryption layers deep.

Why This Matters for Secret Management

The net effect is that Redshift gets four properties out of a single protocol mechanism:

  • Content is NIP-44 encrypted. Relays see ciphertext.
  • Sender identity is hidden behind a throwaway key. No attribution without decryption.
  • Timestamps are randomized, so you can't correlate activity patterns.
  • Relays have no way to distinguish secret storage events from any other Gift Wrap traffic.

NIP-44 Encryption Details

NIP-44 handles the actual cryptographic operations inside the seal and gift wrap layers. The construction looks like this:

Key agreement uses secp256k1 ECDH between the sender and recipient Nostr keypairs, producing a shared secret. That shared secret is expanded via HKDF into the actual encryption key and nonce. The payload is encrypted with XChaCha20 and authenticated with HMAC-SHA256. Messages are also padded to fixed lengths to prevent content-length analysis.

This is a solid, modern construction. If an attacker compromises a relay and dumps everything, they get a pile of padded, authenticated ciphertext with no useful metadata attached. They'd need the recipient's private key to make any progress.

Decryption Flow

When Redshift fetches your secrets, it peels the layers off in reverse:

  1. Query relays for kind 1059 events with a p tag matching your pubkey
  2. NIP-44 decrypt the gift wrap content using the throwaway pubkey + your private key, revealing the seal
  3. Verify the seal's pubkey matches your own (for self-stored secrets) or a trusted sender
  4. NIP-44 decrypt the seal content, revealing the unsigned rumor
  5. Parse the rumor's content and tags to extract the actual secret values

Everything happens client-side. Your private key never leaves your device, and plaintext secrets exist only in memory during the session.

Security Considerations

A few things to keep in mind when reasoning about this threat model:

Key compromise is total. If someone gets your nsec, they can decrypt every secret ever encrypted to your pubkey. There's no forward secrecy here; NIP-59 uses static ECDH, not a ratcheting protocol. Rotate secrets if you suspect key compromise.

Relay availability is your responsibility. Relays can go offline, purge old events, or refuse to store your data. Use multiple relays. Redshift publishes to several by default, but you should verify your relay list periodically.

The throwaway key is per-event. Reusing it across messages would re-link them. Redshift generates a fresh keypair for every Gift Wrap, which is the correct behavior per the spec.

Further Reading

You can inspect this in practice by creating a Redshift project and watching the network tab. The kind 1059 events will have random pubkeys and no readable content.

For the full spec, see NIP-59 on GitHub. Our security docs cover Redshift's specific implementation choices.

Ready to try Redshift?

Own your secrets with decentralized, censorship-resistant secret management.