Skip to main content

Private messaging

Softchat private messages have two distinct layers:

  1. a rumor carries the author, event kind, timestamp, exact tags, content, and deterministic ID without a signature; and
  2. a NIP-59 seal and gift wrap authenticate and privately route that rumor to one recipient.

Never expose a rumor recovered from the network until unwrapGiftWrap authenticates the outer route, outer signature, ciphertext, seal signature, seal shape, rumor ID, and author binding.

Create a one-to-one message

The minimum NIP-17 helper creates kind 14 with exactly one canonical p tag. Group messages, replies, attachments, and extension tags use the richer chat event API.

The lower-level createRumor operation is available when the application has already assembled another accepted event draft:

let message = alice.create_nip17_text_message(
&bob.public_key(),
1_700_000_000,
"hello Bob",
)?;

Wrap recipient and sender copies

Call giftWrap separately for every recipient and for the author’s own recoverable history. Each call creates a fresh wrapper key, exactly one outer p tag, empty seal tags, and independently randomized seal/wrapper timestamps within the previous two days.

let recipient_copy = alice.gift_wrap(
&bob.public_key(),
message.rumor(),
Nip59EnvelopeKind::Durable,
now,
)?;
let sender_copy = alice.gift_wrap(
&alice.public_key(),
message.rumor(),
Nip59EnvelopeKind::Durable,
now,
)?;

Receive and classify

let envelope = bob.unwrap_gift_wrap(&recipient_copy)?;
let received = Nip17TextMessage::from_rumor(envelope.into_rumor())?;
assert_eq!(received.content(), "hello Bob");

Durable and ephemeral envelopes

WrapperEvent kindUsePersistence
Durable1059messages, account copies, durable changespersist and retry under host policy
Ephemeral21059typing and other transient signalsdo not persist as history

A wrong recipient, altered wrapper, altered seal, or mismatched rumor produces the same stable invalid-envelope failure. Do not reveal which layer failed.