# How NIP-17 Gift-Wrapping Enhances Privacy for Nostr Messages in BitChat

> Discover how NIP-17 gift-wrapping in BitChat boosts Nostr message privacy using ephemeral keys and NIP-44 v2 encryption. Learn how relays verify messages without reading content.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-07-27

---

**NIP-17 gift-wrapping in BitChat encrypts private messages using ephemeral X25519 keys and NIP-44 v2 encryption, wrapping them in an outer envelope that relays can verify without reading the content, ensuring only the intended recipient can decrypt the payload.**

BitChat implements the NIP-17 gift-wrapping protocol to provide end-to-end encryption for direct messages on the Nostr network. This approach wraps sensitive payload data inside an outer cryptographic envelope, preventing relay operators and third-party observers from accessing message content while maintaining the decentralized nature of the protocol.

## How NIP-17 Gift-Wrapping Works in BitChat

The implementation follows a strict separation between the outer envelope (visible to relays) and the inner encrypted payload (readable only by the recipient).

### Envelope Construction with Ephemeral Keys

In [`bitchat/Nostr/NostrProtocol.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Nostr/NostrProtocol.swift), the `createPrivateMessage` method constructs the gift-wrapped structure. The process begins by generating an **ephemeral X25519 key pair** unique to this specific message. This ephemeral key ensures that each gift-wrap uses a distinct encryption key, preventing correlation attacks across multiple messages.

The inner event containing the actual message content is encrypted using **NIP-44 v2** symmetric encryption via `XChaCha20Poly1305Compat.encrypt`, utilizing the recipient's public key. This guarantees that only the holder of the corresponding private key can decrypt the payload.

### The Outer Gift-Wrap Layer

The encrypted payload is placed inside a new outer event with `kind: 1059` (the standardized kind number for NIP-17 gift wraps) and `kind: .giftWrap` in the Swift implementation. The outer event is signed by the sender's identity, allowing relays to verify authenticity without accessing the inner content. This separation ensures that infrastructure operators see only routing metadata while the content remains confidential.

## Inbound Processing and Security Controls

The receiving pipeline implements strict verification and replay protection before exposing decrypted content to the UI.

### Verification and Decryption in NostrInboundPipeline

In [`bitchat/ViewModels/NostrInboundPipeline.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/ViewModels/NostrInboundPipeline.swift), the `handleGiftWrap` method processes incoming kind 1059 events. The pipeline first verifies the outer envelope's cryptographic signature to ensure authenticity. It then checks the event ID against a processed events cache to prevent duplicate handling.

After verification, the system offloads decryption to a background actor. The method extracts the ephemeral public key from the envelope and uses the recipient's private key to derive the shared secret, decrypting the inner NIP-44 v2 payload. The decrypted content then routes to the appropriate private chat view.

### Replay Attack Prevention

BitChat implements strict replay protection through event ID tracking. The `context.hasProcessedNostrEvent(giftWrap.id)` guard clause in [`NostrInboundPipeline.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrInboundPipeline.swift) (lines 89-110 and 293-311) silently ignores duplicate event IDs. This prevents attackers from replaying captured gift-wraps across multiple relays to induce duplicate processing or extract metadata through timing analysis. Unit tests in [`ChatNostrCoordinatorContextTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatNostrCoordinatorContextTests.swift) (lines 357-376) verify that duplicate gift-wraps are ignored on replay.

## Code Implementation Examples

The following Swift code demonstrates the gift-wrapping workflow based on the BitChat test suite:

```swift
// Create a gift-wrapped private message using ephemeral keys
let recipient = try NostrIdentity(pubkeyHex: "<recipient-pubkey>")
let giftWrap = try NostrProtocol.createPrivateMessage(
    id: UUID().uuidString,
    kind: .textNote,
    content: "Hello from gift wrap",
    tags: [["p", recipient.pubkey]],
    createdAt: Date()
)

// Publish to Nostr relays
try context.sessionFactory.latestConnection(for: relayURL)?
    .emitEventMessage(subscriptionID: "gift-wraps", event: giftWrap)

```

On the receiving side, the pipeline automatically handles decryption:

```swift
func handleIncoming(_ event: NostrEvent) {
    // Verifies signature, checks for duplicates, and decrypts in background
    inboundPipeline.handleGiftWrap(event, id: recipient)
}

```

Unit tests in [`bitchatTests/NostrProtocolTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/NostrProtocolTests.swift) (lines 26-34 and 351-363) verify the encryption format, specifically checking for the `"v2:"` prefix that denotes NIP-44 v2 encryption. Additional tests in [`bitchatTests/Services/NostrTransportTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/NostrTransportTests.swift) validate geohash-based private message routing using the gift-wrap mechanism handled by `NostrInboundPipeline.subscribeGiftWrap`.

## Summary

- **NIP-17 gift-wrapping** encrypts message content using ephemeral X25519 keys and NIP-44 v2 via `XChaCha20Poly1305Compat.encrypt`, wrapping it in a kind 1059 envelope.
- **Ephemeral keys** ensure each message uses unique encryption parameters, preventing correlation attacks.
- **Relay verification** works on the outer envelope signature alone, keeping content hidden from infrastructure operators.
- **Replay protection** via `hasProcessedNostrEvent` prevents duplicate processing and replay attacks.
- **Background decryption** in [`NostrInboundPipeline.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrInboundPipeline.swift) (lines 89-110 and 293-311) ensures UI responsiveness while maintaining security.

## Frequently Asked Questions

### What is NIP-17 gift-wrapping in Nostr?

NIP-17 gift-wrapping is a privacy protocol that encrypts private messages inside an outer Nostr event envelope. The outer event uses kind 1059 and contains encrypted payload data that only the intended recipient can decrypt, while relays can still verify the sender's signature on the outer wrapper to ensure authenticity.

### How does ephemeral key encryption improve privacy in BitChat?

Each gift-wrapped message generates a fresh X25519 ephemeral key pair during creation in `NostrProtocol.createPrivateMessage`. This means compromised keys from one message cannot decrypt other messages, and observers cannot correlate multiple messages as originating from the same sender based on encryption parameters or key fingerprints.

### Can relays read the content of NIP-17 gift-wrapped messages?

No. Relays only see the outer envelope with metadata necessary for routing (recipient public key, timestamp, signature). The actual content remains encrypted with NIP-44 v2 using `XChaCha20Poly1305Compat.encrypt` until decrypted by the recipient's client, keeping message content confidential from relay operators and network observers.

### How does BitChat prevent replay attacks on gift-wrapped messages?

BitChat records every processed event ID using `context.hasProcessedNostrEvent(giftWrap.id)` in [`NostrInboundPipeline.swift`](https://github.com/permissionlesstech/bitchat/blob/main/NostrInboundPipeline.swift). If the same gift-wrap appears on multiple relays or is resent maliciously, the duplicate ID detection silently drops the event, preventing replay attacks while maintaining efficient processing.