On this page
Abstract
A comment update carries the mutable state of a comment — vote counts, reply counts and reply pages, moderation flags, the latest author edit, and the author’s standing in the community — separately from the immutable comment record (BSIP-2). Comment updates are signed by the community and republished as the comment accumulates votes and replies. This BSIP defines the comment update format, how it binds to its comment, how clients fetch it, and the reduced form used during publishing and in the moderation queue.
Motivation
A comment record is content-addressed and therefore immutable: once published, its CID cannot change. But votes, replies, and moderation decisions change over time. Putting that mutable state in a separate, community-signed record lets the comment stay stable while its counts and status evolve, and lets clients fetch “the current state of this comment” without re-fetching or invalidating the comment itself.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Overview
For each comment it has accepted and published, the community maintains a comment update and
republishes it when the comment’s state changes (for example a new vote, reply, edit, or moderation
action). A comment update is bound to its comment by cid and signed by the key that signs the
community record (BSIP-3).
Fields
REQUIRED fields:
cid— the CID of the comment this update describes. It is signed, so an update cannot be transplanted onto a different comment.upvoteCount— non-negative integer.downvoteCount— non-negative integer.replyCount— non-negative integer; the number of counted replies at every depth under the comment (see Counting replies).updatedAt— integer Unix time in seconds of this version of the update.protocolVersion— string, currently"1.0.0".signature— the community’s signature (see Signing).
OPTIONAL fields:
childCount— non-negative integer; the counted direct replies only.number,postNumber— positive integers; the community-assigned sequential number of the comment and of its thread’s post.replies— a reply pages object (BSIP-3, Pages).lastChildCid— CID of the most recent counted direct reply.lastReplyTimestamp—timestampof the most recent counted reply at any depth.edit— the most recent author edit of the comment, exactly as the author’scommentEditpublication (BSIP-7). Itssignature.publicKeyMUST equal the comment’ssignature.publicKey. Itscontent,deleted,flairs,spoiler, andnsfwoverride the comment’s own values when displayed.flairs— moderator-assigned flairs, overriding the author’s and the edit’s.spoiler,nsfw— moderator overrides of the author’s values.pinned,locked,archived,removed— moderation state (BSIP-8). Only posts can be locked or archived.approved— present once a moderator approved (true) or disapproved (false) a comment that was pending approval. A disapproved comment that is kept also hasremoved: true.reason— string: the reason given by the most recent moderation that carried one (BSIP-8).author—{ "community": ... }, the author’s standing in this community (see Author community standing). No otherauthorfield is allowed.
A comment update MAY carry additional fields set by the community’s challenges (for example a
country code); such fields are signed like any other, and clients MAY ignore those they do not
understand. A comment update MUST NOT carry fields reserved for comment records or runtime use, such
as depth, thumbnailUrl, or deleted.
Example (abbreviated):
{
"cid": "QmQwin4X1VAmELE5KsDHBfz3ZhaLiN8NcH663nvBJNMqUm",
"upvoteCount": 10,
"downvoteCount": 5,
"replyCount": 3,
"childCount": 3,
"number": 42,
"postNumber": 42,
"updatedAt": 1728174027,
"lastChildCid": "Qm…",
"lastReplyTimestamp": 1728450341,
"author": {
"community": { "postScore": 12, "replyScore": 4, "firstCommentTimestamp": 1700000000, "lastCommentCid": "Qm…" }
},
"replies": { "pages": { "best": { "comments": [/* { comment, commentUpdate } … */] } } },
"protocolVersion": "1.0.0",
"signature": {
"type": "ed25519",
"signature": "<base64>",
"publicKey": "<base64, 32 bytes>",
"signedPropertyNames": [
"cid", "upvoteCount", "downvoteCount", "replyCount", "childCount", "number", "postNumber",
"updatedAt", "lastChildCid", "lastReplyTimestamp", "author", "replies", "protocolVersion"
]
}
}
Counting replies
replyCount and lastReplyTimestamp cover every depth under the comment; childCount and
lastChildCid cover direct replies only. A reply is counted once the community has published a
comment update for it, unless it was removed by a moderator, deleted by its author, is pending
approval, was disapproved, or belongs to a different community. Present replies under a removed or
deleted reply are still counted. Removing or deleting a reply removes it from lastReplyTimestamp, so
it no longer bumps its thread.
Author community standing
author.community carries the community’s view of the comment’s author:
postScore,replyScore— REQUIRED. Numbers: the author’s post and reply karma (upvotes minus downvotes) in this community.firstCommentTimestamp— REQUIRED. Timestamp of the author’s first comment in the community, used by account-age rules (BSIP-3).lastCommentCid— REQUIRED. CID of the author’s most recent comment in the community.banExpiresAt— if present and in the future, the author is banned in this community until this time (BSIP-8).flairs— author flairs set by a moderator or a challenge; they override the author’s ownauthor.flairswhen displayed.
Additional fields MAY be present. When the community pseudonymizes authors
(BSIP-2), karma, firstCommentTimestamp, and lastCommentCid describe
the alias, while banExpiresAt and flairs also reflect moderation aimed at the original author
(BSIP-8).
Fetching comment updates
A post’s comment update is published in the community’s postUpdates directories
(BSIP-3). The keys of postUpdates are time buckets in seconds. A post is published
in the smallest bucket b for which now − b ≤ post.timestamp, at the path
/ipfs/<postUpdates[b]>/<postCid>/update
where update is the comment update serialized as JSON. A client SHOULD try the buckets that satisfy
the inequality from smallest to largest, since the community moves a post into a larger bucket as it
ages. The current implementation uses the buckets 86400 (one day), 604800 (one week), 2592000 (30
days), and 3153600000 (100 years).
A reply’s comment update is not published at its own path. Clients obtain it from the reply pages of its parent or of its thread’s post, where each entry pairs the reply with its latest comment update (BSIP-3, Pages).
A client MUST prefer the update with the greater updatedAt when it holds more than one for the same
comment.
Reduced comment updates
A reduced comment update is used inside the CHALLENGEVERIFICATION returned to a publisher
(BSIP-4) and in moderation-queue pages (BSIP-3). It
contains cid, signature, protocolVersion, and optionally author, number, postNumber, and
pendingApproval (boolean, true when the comment is held for moderator approval), plus any fields
the community’s challenges set. It is signed like a full update, over the fields it carries.
Signing
A comment update is signed following the rules of BSIP-2, Signatures, by the
key that signs the community record. Every field of the update except signature MUST be listed in
signedPropertyNames. Because cid is signed, a valid update is bound to exactly one comment.
Verification
A client MUST reject a comment update that:
- carries a field not listed in
signedPropertyNames, or a reserved field; - has an invalid signature;
- is signed by a key other than the community record’s
signature.publicKey(for a delegated community, the current minter; BSIP-11); - has a
ciddifferent from the CID of the comment it is displayed with; - carries an
editwhosesignature.publicKeydiffers from the comment’ssignature.publicKey, whosecommentCiddiffers from the update’scid, or whose signature is invalid; - carries
repliespages that do not verify (BSIP-3, Signing). A preloaded flat sort under a post holds the post’s whole subtree, so its entries are checked against the post rather than as direct replies.
Rationale
- Separating mutable from immutable state is what lets Bitsocial avoid a blockchain: the comment is a stable content-addressed object, and only the small, community-signed update changes over time.
- Signing
cidinto the update prevents a valid update from being replayed onto a different comment. - Community-signed counts make the community the authority on its own vote tallies and moderation — consistent with the community being the unit of moderation (BSIP-3) — while votes themselves remain author-signed (BSIP-6).
- Time buckets for post updates let a client find a post’s latest update from the community record alone, while the community republishes recent, frequently changing posts more often than old ones.
Backwards Compatibility
Older updates MAY carry author.subplebbit instead of author.community; readers SHOULD treat the two
the same. Updates published before childCount and lastChildCid existed omit them.
Security Considerations
- Community trust boundary. Counts, moderation flags, and author standing are asserted by the community. They are authoritative only within that community and MUST NOT be treated as global facts about a comment or author.
- Edit authenticity. An
editembedded in an update MUST be signed by the comment’s signing key; a community cannot forge author edits, only attach genuine ones, and checkingedit.commentCidstops it from moving an author’s edit onto another of their comments. Under pseudonymity the signing key is the alias, which the community holds, so it can forge edits of pseudonymized comments (BSIP-7). - Binding to a comment. Verifiers MUST reject an update whose signed
ciddoes not match the comment they are displaying, or whose signer is not the community’s. - Staleness and rollback. A community or relay can serve an older, validly signed update. Clients SHOULD ignore an update older than one they already trust for the same comment.
- Challenge-set fields. Fields added by challenges are community-asserted like everything else in the update; clients MUST NOT treat them as author claims.
Copyright
Copyright and related rights waived via CC0.