Skip to content

fix(lists): kind 30005 writes prose into .content, which NIP-51 reserves for private items #7588

Open

Description

@realmeylisdev

Problem

CuratedListService publishing a public curated list writes the description into .content (mobile/lib/services/curated_list_service.dart:1191-1193):

if (list.isPublic) {
  content = list.description ?? 'Curated video list: ${list.name}';
  tags = CuratedListConverter.toEventTags(list);
}

NIP-51 puts a set's description in a tag — "sets can optionally have a title, an image and a description tags" — and reserves .content for the NIP-44-encrypted private-item array. The converter already emits that tag (mobile/packages/curated_list_repository/lib/src/curated_list_converter.dart:190-191), so the prose in .content is both redundant and protocol-wrong.

This is the same defect PR #6966 fixed for kind 10003, where .content had been the literal "Divine global bookmarks".

Data-loss path

NIP-51 allows one list to carry public and private items simultaneously — the spec's own mute-list example does. For a kind-30005 with public e/a tags and another client's ciphertext in .content, unsealItemTags short-circuits before decrypting (mobile/lib/services/curated_list_relay_gateway.dart:484):

if (event.content.isEmpty || _hasPublicItemTags(event.tags)) {
  return const UnsealedItemTags.notSealed();
}

Divine then treats the list as public, and the next edit overwrites .content with the description — destroying private items it never read. Kind 30005 is addressable/replaceable, so the overwrite is total.

Suggested fix

Publish a public list with content: '' and rely on the existing description tag. Carry any existing .content through verbatim when the private items are not what is being changed, mirroring how BookmarkService handles kind 10003 (mobile/lib/services/bookmark_service.dart:722-728).

Note curated_list_converter.dart:107 currently falls back to event.content as the description when reading a non-private list, so the read path needs to keep tolerating legacy events that still carry prose there.

Not measured

The incidence of mixed public+private kind-30005 lists in the wild has not been measured, so the data-loss path is demonstrated from the code rather than from an observed occurrence.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions