Revision Gift Wrap
draft
Related drafts:
Summary
Comet should keep encrypted kind:1059 gift wraps for sync payload transport, but add a stable logical revision envelope in the outer tags.
The goal is to separate:
- payload privacy
- document identity
- revision identity
This lets Comet support multi-relay sync and revision-aware anti-entropy without making the outer gift wrap deterministic.
Goals
- Preserve the privacy properties of gift wraps
- Give each logical note revision a stable identity across relays
- Support tombstones and merge revisions
- Support revision-aware Negentropy and revision-aware
CHANGES
Non-Goals
- Replacing NIP-59 with a new event kind
- Making ciphertext or outer event IDs stable across relays
- Exposing plaintext note content on the relay
Problem
Today’s outer gift-wrap event ID is not a good multi-relay identity because it changes on each publish.
That is expected because the wrap currently uses:
- a fresh ephemeral wrapping key
- a random NIP-44 nonce
- a tweaked timestamp
Those are good privacy properties. Comet should keep them.
The missing piece is a relay-visible logical revision identity that stays stable even when the outer event ID changes.
Event Shape
Comet should continue to publish encrypted sync payloads as kind:1059 gift wraps.
The outer tags should include:
["p", <recipient_pubkey>]["d", <doc_id>]["r", <32-byte-hex revision id>]["prev", <parent_revision_id>] // repeatable["op", "put" | "del"]["m", <modified_at_ms_as_string>]["type", "note" | "notebook"] // optional, relay-visible hint["v", "2"]Field meanings:
p: recipient pubkey for the gift wrapd: stable logical document ID within the recipient-specific namespacer: stable logical revision ID for this exact document stateprev: parent revision ID; a merge revision may include more than oneop: normal content revision or tombstone revisionm: logical revision timestamp used for ordering hintstype: optional relay-visible logical sync entity type
For Comet itself, this outer type hint should normally be omitted for privacy. The authoritative entity type lives only inside the encrypted payload. Other apps may choose to publish an outer type if they want relay-visible classification.
v: Comet sync schema version
For this extension, the outer p tag is not just transport metadata. It defines the recipient-specific namespace in which d, rev, heads, and anti-entropy scope are evaluated.
That means d is not globally unique by itself. Document identity is (recipient, d), and revision identity is (recipient, d, rev).
Revision Identity
rev must not depend on the outer Nostr event ID.
Recommended rule:
rev = HMAC(sync_secret, canonical_revision_payload)The canonical revision payload should include:
d- sorted
prevlist op- logical note or notebook fields
- attachment references
- schema version
Properties of this approach:
- stable across relays
- opaque to the relay
- changed by any meaningful document mutation
- usable as a 32-byte anti-entropy ID
Blob Identity
Comet keeps attachment references and stored blob objects intentionally separate.
Within the encrypted note payload:
- markdown references attachments as
attachment://<plaintext_hash>.<ext> blobtags carry(plaintext_hash, ciphertext_hash, encryption_key_hex)
That split is intentional:
plaintext_hashidentifies the logical attachment reference inside Comet markdownciphertext_hashidentifies the encrypted object stored on Blossomencryption_key_hexlets the client decrypt the downloaded ciphertext back into the plaintext attachment bytes
This means the same attachment can remain addressable inside the note by its plaintext hash, while the server only stores and serves the encrypted ciphertext object.
Local clients should persist a bridge record such as blob_meta that maps:
plaintext_hash -> (server_url, ciphertext_hash, encryption_key)When debugging:
- if the editor or markdown contains
attachment://..., that hash is the plaintext hash - if a Blossom URL or server object lookup is involved, that hash is usually the ciphertext hash for revision-sync blobs
- public publish flows that rewrite markdown to direct Blossom URLs may use plaintext hashes in the final URL instead of ciphertext hashes
Revision Rules
Immutable revisions
Each (p, d, rev) tuple represents one immutable logical revision.
The relay should not delete older revisions just because a newer revision for the same document arrives.
Tombstones
Logical deletion should be represented as a normal revision with:
["op", "del"]A tombstone is the current document head until a newer revision supersedes it.
For Comet, the outer op is the authoritative deletion signal. The inner encrypted payload does not need a second deleted=true marker. It only needs to carry the inner document id and inner type so the client can interpret what logical entity is being deleted.
Merge revisions
A merge revision is any revision with multiple prev tags.
That allows Comet to move from timestamp-only LWW toward ancestry-aware merges without changing the transport format again.
Head Semantics
The outer gift wrap stores one immutable revision.
The logical head set for a document is defined by the revision graph:
- a head is any stored revision not referenced by another stored revision for the same
(p, d) - one head means the document has a single current state
- multiple heads mean a true conflict branch
For relay implementation, heads should be materialized on write and treated as a first-class read model. Rebuilding heads from the revision graph is a repair operation, not the normal read path.
Relay Expectations
To support revision-aware sync, the relay should:
- retain immutable revisions keyed by
(recipient, document_coord, rev) - store parent edges for
prev - materialize the current head set
- allow filtering by the revision tags used in sync
Recommendation:
- keep the relay-queryable wire surface to single-letter tags such as
p,d,r, andm - map those tags onto descriptive internal schema fields like
recipient,document_coord,rev, andmtime
Non-queryable metadata such as prev, op, and type can remain descriptive until there is a real need to expose them as indexed relay filters.
Security Considerations
Do not try to make the outer gift wrap deterministic.
That would require removing or constraining the randomness in:
- the ephemeral wrapping key
- the encryption nonce
- the outer timestamp
Doing that would trade away privacy for transport identity. The better design is:
- stable logical identity in outer tags
- private payload in the encrypted body
Client Expectations
The client should treat:
das the logical document coordinaterevas the logical version identityprevas ancestryop=delas a logical deletion marker
The client should not use the outer event ID as the durable logical identity for a revision.
Open Questions
- Whether non-queryable metadata tags such as
prev,op, andtypeshould also move to single-letter aliases later - Whether large merge histories should remain fully explicit in outer tags
- Whether future interop needs a second deterministic manifest event type in addition to the private gift wrap
Final Recommendation
Comet should keep kind:1059 gift wraps, but upgrade them from “private replaceable payloads” to “private immutable revision carriers” by adding stable outer revision tags.