Skip to content

concord --invites reports 0/NONE for v2 Direct Invites (kind 3313) #76

Description

@JAFairweather

Summary

concord --invites is documented (crates/concord-cli/src/main.rs:15) as answering
"is a direct invite actually on the network for this account?" — but it false-negatives on
v2 Direct Invites. Run against an inbox holding a real 3313 direct invite, it unwraps the
wrap, tallies the rumor as kind 3313, and then prints:

inner-kind tally (unwrapped):  kind 3313  ×N
── community invites on network: 0 ──
   NONE. No COMMUNITY_INVITE_BUNDLE gift wrap for this account on these relays in-window.

The verdict contradicts the tally printed two lines above it.

The wrap is not untrusted, either: probe_invites opens it via
UnwrappedGift::from_gift_wrap, which Schnorr-verifies the seal and binds
rumor.pubkey == seal.pubkey (nostr fork pinned at 0f9f3a7, nip59.rs:108/:115),
and the tool prints a signature-verified distinct senders seen: 1. So the invite is
discarded purely on kind/shape — "couldn't authenticate the wrap" is not the explanation.

Reproduced on two independent accounts / inboxes (different keys, different wraps), same result.

Root cause — a v1-only probe path, three layers

  1. main.rs:571 — the classifier is if k == COMMUNITY_INVITE_BUNDLE (3304, the v1 bundle).
    A v2 Direct Invite rumor is DIRECT_INVITE = 3313 (crates/vector-core/src/community/v2/mod.rs:65),
    so it never enters the invite branch.
  2. crates/vector-core/src/community/invite.rs:184 — even if the filter is widened,
    parse_invite_rumor also hard-rejects any kind but 3304, and parses the content with
    the v1 CommunityInvite::from_json. A v2 rumor's content is the v2 bundle shape
    (CommunityInvite::from_bundle_json), a different deserializer — so a one-line filter fix
    moves the failure one call down rather than fixing it.
  3. crates/vector-core/src/community/v2/invite.rs:607 — the correct v2 entrypoint
    unwrap_direct_invite(wrap, keys) already exists in the same crate and does the full job:
    gift-wrap kind gate → seal decrypt → seal.verify() → rumor decrypt →
    rumor.kind == DIRECT_INVITErumor.pubkey == seal.pubkey bind → from_bundle_json.
    probe_invites never calls it; it re-implements a weaker, v1-only unwrap beside it.

Suggested fix

For each gift wrap whose inner rumor kind is 3313, route it through
vector_core::community::v2::invite::unwrap_direct_invite(wrap, &keys) (which returns
(sender, CommunityInvite) with provenance already checked) instead of the v1
parse_invite_rumor. Keep the v1 3304 path for backward compatibility. This also restores
the sender/community/channel detail the current path drops for v2 invites.

Notes for the maintainer

  • Binary is concord ([[bin]] name = "concord"), not concord-cli.
  • The --invites NONE line and the inner-kind tally are the two artifacts to compare when repro'ing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions