Protocol
This document specifies the Kiwi News Protocol.
Message ID generation
What is a Message
- A message is every JSON object that complies with the Message JSON schema.
- Every message must carry a valid EIP-712 signature. Any address can sign for itself; a message signed by a delegated key is attributed to the address that delegated to it. (Before 0.12.0, the signer also had to hold the Kiwi Pass NFT.)
- A message must have a UNIX
timestampgreater or equal than 1672527600 (2022-12-31T23:00:00+00:00). - A message must have a UNIX
timestamplesser than 60 seconds into the future.
Message types
Messages are signed with EIP-712 in the domain {name: "kiwinews", version: "1.0.0", salt: keccak256("kiwinews domain separator salt")}. There are five kinds:
- amplify: a submission or an upvote. The first amplify of a link is its submission; later ones are upvotes. Signed as
Message(string title,string href,string type,uint256 timestamp). - amplify with
community(since protocol 13.0.0): a submission to a community, see Communities. Signed asMessage(string title,string href,string type,uint256 timestamp,string community). The fifth field gives it its own type hash, so a signature can't move between the two. - comment:
hrefiskiwi:0x<index of the story>. Signed as the 4-fieldMessage. - community (since protocol 13.0.0): a user community, written by its owner, see Communities. Signed as
Community(string type,string name,string about,string[] rules,string icon,string color,uint256 timestamp). - join and leave (since protocol 14.0.0): joining or leaving a community, see Membership. Signed as
Membership(string type,string community,uint256 timestamp).
Process
- "Canonicalize" the Message by expressing the object the format of RFC 8785 JSON Canonicalization Scheme (JCS).
- Encode the message to CBOR (using cbor-x).
- Hash the message using keccak-256 and express it as hexa-decimal number (without a "0x" prefix.
- Prefix the message hash with a hexa-decimal UNIX timestamp
Considerations
- The identifier must be of fixed length.
- The identifier must be lexographically orderable as to order all messages ascendingly from "left to right" when inserting them in the Merkle Patricia Trie.
Rationale
- We use "canonicalize" and "cbor-x" because of subsequent historical decisions and because they both refer to complex but canonical RFCs that we hope are good enough to allow someone to re-implement the Kiwi News Protocol in another programming language.
- We use "canonicalize" because "cbor-x" isn't canonicalizing a JSON object by itself.
- We use keccak-256 to be compatible with the Ethereum ecosystem.
- We prefix the message hash with a hexa-decimal UNIX timestamp to create a lexographic total order.
- We're not left-padding the UNIX timestamp as it will only require an additional digit on November 20, 2286.
Reference Implementation
Communities
Since protocol 13.0.0, anyone can run a community. A community belongs to the identity that signs its Community message (the signer, or the account that delegated to the signer) and lives in that identity's namespace, so two people can both run a community called "AI". Creating one needs no consensus: nobody but the owner can write to an owner's namespace.
Fields
type:"community".name: 1 to 24 characters, words of letters and digits separated by single spaces or hyphens.about: up to 100 characters on one line.rules: up to 5 house rules of 1 to 140 characters, each on one line.icon: a Phosphor Icons 2.1.1 name, as listed insrc/phosphor-icons.mjs. Clients draw the icon from their own copy of the set.color: any hex colour,#RRGGBB. The protocol has no opinion on it: clients may offer a palette and decide how to show other colours, e.g. in dark mode.timestamp: as for every message.
Adding icons is a protocol change, as nodes with the older list reject the new names.
IDs and updates
- A name's key is the name in lowercase with spaces as hyphens: "Agent Builders" has the key
agent-builders. - A community's ID is
<owner address>/<name key>, e.g.0x0f6a79a579658e401e0b81c6dde1f2cd51d97176/agent-builders. - Signing a
Communityunder a name key you already use updates that community. Every version stays in the trie. Readers show the newest one: the greatest timestamp, and of equal timestamps the greatest message ID. - A node resolves a version's signer to its owner with the delegations it knows when it judges a post or shows the community, not when it stored the version. A delegation that reaches a node later (they take a minute or two to be indexed) then still makes the community its account's. Versions signed by an account and by its delegated keys under the same name are one community.
- There's no deleting or transferring a community, and no moderation on the protocol. Whoever loses the owning account can't change the community anymore.
Posting to a community
- A post to a community is an amplify with
communityset to the community's ID. The address part may be in any case. - A node accepts it only if it has a version of that community older than the post, as a comment's story must be older than the comment. Otherwise the node drops it. When the community arrives later, set reconciliation offers the post again. So a client posting to a community it just created uses a timestamp at least one second after the community's.
- If the link already is a story, the post counts as an upvote of that story, wherever the story lives.
- Upvotes and comments don't name a community. A story stays in the community of its first submission.
Membership
Since protocol 14.0.0, joining and leaving a community are signed messages, so every node knows who is a member and a person's communities follow them from device to device.
- A membership message has three fields:
type,"join"or"leave";community, the community's ID (the address part in any case); andtimestamp. It is signed asMembership(string type,string community,uint256 timestamp). - A node accepts it only if it has a version of that community older than the message, the same rule as for posts to a community. It doesn't look at the signer's earlier joins and leaves: whether a node takes a message never depends on the order in which messages arrive, so set reconciliation converges. Leaving a community you never joined is a valid message.
- A person is an identity: a membership signed with a delegated key counts for the account that delegated to it, resolved with the delegations a node knows when it counts, like a community's owner.
- A person's newest membership message for a community decides whether they're a member: the greatest timestamp, and of equal timestamps the greatest message ID. Every message stays in the trie, so joining again after leaving is a new message.
- The owner of a community is always a member, whether or not they signed a join, and leaving their own community changes nothing.
- A community's member count is its owner plus every other person whose newest membership message for it is a join.
Reference implementation
src/communities.mjs,src/memberships.mjs,src/constants.mjs(EIP712_COMMUNITY,EIP712_COMMUNITY_MESSAGE,EIP712_MEMBERSHIP, thecommunityandmembershipschemas) andsrc/store.mjs(_add).