Social media is becoming the lens through which our society perceives the world around it. It shapes our views about our friends, colleagues, and current events in the world around us. Social media companies serve as trusted third parties that perform three functions: help billions of users establish identities, collect their updates in real-time and distribute them to peers through user-friendly apps.
Social platforms emerge as natural monopolies due to network effects. Once a network reaches critical mass, it becomes tough to compete with, leaving users with no practical alternatives. The incentive to operate in a user's best interests weakens, and the outcomes follow. Moderation and curation policies maximize ad revenues at the expense of user needs, private data isn't protected as well as it should be, and APIs for developers become restricted or non-existent.
A sufficiently decentralized network could align incentives between the network and its users. It can use cryptography to establish user identities and a peer-to-peer network to send data between users. Trusted third parties are unnecessary as any developer can build an app, and users can switch between them anytime. While it is harder to design and operate such a network, the aligned incentives will lead to much better long-term outcomes.
Federated networks like ActivityPub achieve a few degrees of decentralization by allowing users to choose a trusted provider. SecureScuttlebutt eliminates servers and relies on a peer-to-peer architecture without trusted parties. Blockchain-based social networks like peepeth and steemit used public ledgers to track network state.
Developments in other areas offer useful building blocks for decentralized social networks. CRDT's allow networks to reach strong eventual consistency without a coordination layer. Layer-2 blockchains show how networks can achieve greater throughput and lower costs by building on top of Layer-1's.
Farcaster是一个构建在以太坊上的去中心化网络,底层区块链Layer 1负责管理用户身份,而二层网络Layer 2负责用户之间的信息传播,这种策略提供了:
- 安全、持久和友好的用户身份,比如
@alice
- 用户之间的信息可以实时传递
- 以合理的成本访问去中心化网络上的所有数据
用户首先在以太坊合约上注册一个公私/密钥对和一个ID。Farcaster中的一条信息必须包括ID和来自私钥对的签名,这使得它具有防篡改和自我认证的功能。信息的接受者可以在合约中查找与ID相关的密钥对,并验证信息的真实性。
用户将签名的信息上传到Farcaster中心,这就像区块链网络中的一个节点。集线器通过点对点协议共享消息,每个集线器都存储网络上的所有消息。集线器使用delta图来达成关于网络状态的共识。由于更新是独立的操作,三角洲图无需协调就能达成共识,形成一个第二层的存储网络。
Farcaster is a decentralized social network built on top of Ethereum. The Layer 1 blockchain manages user identities, while a Layer 2 network propagates updates between users. It offers:
- Secure, memorable, and human-readable user identifiers like
@alice
- Real-time settlement and propagation of changes between users.
- Decentralized access to all data on the network at reasonable costs.
Users start by registering a public-private key-pair and an id with an Ethereum contract. A message must include id the and a signature from the key-pair, which makes it tamper-proof and self-authenticating. Recipients can look up the key-pair associated with the id in the contract and verify the message's authenticity.
Users upload signed messages to a Farcaster Hub, which is like a node in a blockchain network. Hubs share messages over a peer-to-peer protocol, and each Hub stores all the messages on the network. Hubs use delta graphs to reach consensus about the network's state. Since updates are independent operations, delta graphs achieve consensus without coordination, forming a layer-2 storage network.
Developers can create desktop and mobile applications for users to interact with the network. Apps connect to Hubs and subscribe to updates from specific parts of the network relevant to the user. They can use servers to cache data, generate feeds, make recommendations and send notifications. Such apps can offer all the features and services that users have come to expect from modern social networks.
graph LR
subgraph L1 Blockchain
id[Farcaster Contracts]
end
subgraph L2 Delta Graph
id---hub1[<center>Farcaster Hub</center>]
id---hub2[<center>Farcaster Hub</center>]
end
hub1---app1[<center>Server</center>]
hub2---app2[<center>Desktop Client</center>]
hub2---app3[<center>Mobile Client</center>]
subgraph App3
app1-.-client1(Desktop Client)
app1-.-client2(Mobile Client)
end
subgraph App2
app2
end
subgraph App1
app3
end
The identity system allows any two users on Farcaster to find each other and establish a secure communication channel. Importantly, this does not need any trusted third party in the mix. Each user's identity has three components:
- A unique, numeric identifier (e.g.
8098
) - An ECDSA key-pair that can sign messages
- An optional, cosmetic username from a name registry (e.g.
alice
)
Users must generate a new Ethereum address which serves as their key pair and use it to acquire an id, and optionally a username. The id is a canonical reference for a user on the Ethereum network. Users may pick a username from any Ethereum-based name registry to serve as a more human-readable reference. Farcaster's Ethereum contracts keep track of each user's id, key pair, and username on-chain. Ethereum's blockchain architecture ensures that ownership of identities remains decentralized and secure.
A Farcaster ID or fid
is the canonical identifier for a user or organization. Fids are numerical values like !8098, distinguished from other numbers by prefixing them with an exclamation mark. Users register fids to an Ethereum address, also known as the custody address
. Users can transfer fids to a new custody address, though each address can own exactly one fid at a time.
Users must call the Farcaster ID Registry contract and pay a small amount of gas to request an fid. The contract issues a new unique, 256-bit unsigned integer which becomes the fid. Anyone can inspect the contract to determine the custody address that owns an fid.
Users can sign a message containing their fid with the ECDSA key pair of their custody address. Recipients can verify its authenticity by looking up the custody address in the ID Registry and verifying the signature. No-one can reclaim or revoke an fid once a custody address claims it. This ensures sufficient decentralization by allowing tamper-proof communication over untrusted networks without requiring trusted third parties.
A Farcaster name or fname
is an optional, human-readable identifier for users and organizations. Fnames are alpha-numeric like @alice, distinguished from other text by prefixing it with an at-symbol. Users can get register an fname to a custody address, which can own more than one name at a time.
Fnames must be unique and match the regular expression /^[a-z0-9][a-z0-9-]{0,15}$/
. While Ethereum has many namespaces, fnames have unique properties that make them very useful in social networks. They are cheaper to mint and own, are less vulnerable to homoglyph attacks, and are recoverable.
User register fnames for a year at a time by paying a fee to the Farcaster Name Registry, which issues each one as an NFT. The protocol's core team periodically sets the fee rate to a value that makes squatting less practical. An fname becomes renewable ninety days before it expires. Once expired, fnames enter a dutch auction where the price is set to a yearly fee plus a premium, which decays until it reaches zero.
Users may appoint a recovery address to protect their fnames and fids in case they lose access to the custody address. The recovery address can request a transfer to a new custody address, executable after a three-day escrow.
Ownership of fids and fnames remains decentralized because unauthorized recoveries are preventable. During the escrow period the owner can cancel a transfer and revoke a malicious recovery address. The request completes only if the user permits it to happen or if they no longer have access to their custody address.
The registry contracts allow recovery addresses to be set at any time and to any address. Users may configure them to point to a backup address, a friend's wallet, a multi-sig, or even a third-party recovery service. This system only protects against address loss, and not address compromise.
A delta graph is a data structure that represents and synchronizes a social network's state between many replicas. The state of the social network is a graph where each vertex is an entity like a user or message, and each edge is a relationship between entities. A network where @alice creates a message and @bob likes and replies to it would produce this graph:
graph LR
M1([Hello World!]) -->|author| A(alice)
M2([Welcome!]) --> |author| B(bob)
M2 --> |reply-to|M1
B --> |likes|M1
style A text-align:left
style B text-align:left
style M1 text-align:left
style M2 text-align:left
The graph decomposes into a series of deltas
- casts, which are messages from a user
- reactions, which are reactions to a user's messages from another user (e.g., a like)
- amps, which are amplifications or endorsements of a user
- verifications, which are proofs of ownership (e.g., an Ethereum address signature)
- signers, which are key pairs authorized by a user to sign their deltas
- user data, which are metadata about the user (e.g., their profile picture)
Each delta is an atomic operation that may add one or more vertices or edges to the social graph. Some deltas may add multiple nodes and edges in a single action. Consider @bob's reply-cast to @alice's cast which must add a new entity (the cast) and set up two relationships, one to bob as the author and the other to alice's cast as the parent. The graph in the example above can be broken down into the following deltas:
flowchart TD
W2(<b>add-cast</b> <br> message: Hello World! <br> author: alice <br><br> )
W3(<b>add-cast</b> <br> message: Welcome! <br> author: bob <br> reply-to: Hello World! <br> )
W1(<b>add-reaction</b> <br> type: Like <br> author: bob <br> target: Hello World! <br> )
style W1 text-align:left
style W2 text-align:left
style W3 text-align:left
Delta graphs must be able to synchronize the social graph across an unreliable network. Synchronization is straightforward if a set of deltas always produce the same graph when combined in any order, and dropped messages can be re-shared between replicas safely without corrupting the graph's state. Our design goal, in formal terms, is to simplify syncing by making deltas commutative, associative, and idempotent across the graph.
Idempotency requires that a delta is not applied to a graph more than once. If @alice likes @bob's cast, but she sends the delta twice by accident, it must not count as two likes. Deltas must include a unique identifier
Commutativity and associativity require that a set of deltas always produce the same graph. If @alice likes @bob's cast and unlikes it later, that generates two conflicting deltas that change the same part of the graph. Deltas must have a conflict identifier
A delta's conflict identifier (bob, like, Hello World!)
is sufficient as a
A CRDT, or conflict-free replicated datatype, stores deltas using the rules described above. Each delta type has its own CRDT, which may include rules specific to the particular type and resembles an anonymous, delta-state CRDT1. The delta graph is therefore a collection of multiple CRDTs which combine to Each CRDTs
Formally, the delta graph
$G = {C_1, C_2,... }$ , CRDT$C ={ \delta_1, \delta_2, ... }$ and$merge(\delta_n, C_n)$ produces$C_n' \geq C_n$ .
Deltas have a timestamp order which derives from user-reported timestamps. Timestamp order is partial since many messages can have the same timestamp. It helps with ordering messages in user interfaces like chronological feeds and profiles. It is untrustworthy since it is user-controlled and has a similar trust model to timestamps observed on websites and blogs.
Deltas also have a lexicographical hash order derived from the hash digest of the operation itself. Lexicographical hash order is total since each message has a unique hash value. It is arbitrary but deterministic, and CRDTs use it in combination with the timestamp order to resolve conflicts.
Deltas may have an optional, causal order set by the user. For instance, a reply cast may contain a reference to its parent cast to help clients reconstruct the conversation thread. Causal ordering is partial since it is optional in some delta types. The ordering is deterministic and meaningful and is useful to applications when rendering content.
Users are only allowed to modify certain parts of the delta graph. For example, @bob can subscribe to @alice but cannot make @alice subscribe to @charlie. Each delta type may define different rules governing what a user can change. Users authenticate deltas by hashing and signing them with an asymmetric key pair. Signatures make the delta tamper-proof, allowing transmission over untrusted networks. CRDTs check the signatures and the segments of the graph modified by the user and only merge changes that pass both validations.
Users must sign every delta with an EdDSA key pair known as a signer. A user can create multiple signers and assign one to each Farcaster application they use. A Signer CRDT tracks each user's valid signer key pairs, and other CRDTs will accept only accept deltas signed by these key pairs. Users add signers by producing a special signer delta signed by their custody address. A signer delta is the only delta that has an ECDSA signature and it forms a chain of trust linking an on-chain identity to an off-chain delta. Users can revoke signers if they suspect a compromise, which evicts all deltas authorized by the signer.
graph LR
Custody([Eth Custody Address]) --> |Signer Delta, ECDSA Sig|SignerA1([Farcaster Signer])
SignerA1 --> |Cast Delta, EdDSA sig|CastC[Hello World!]
SignerA1 --> |Cast Delta, EdDSA sig|CastD[It's Alice!]
A delta-graph that can grow forever will adversely impact decentralization by making a hub's storage costs very expensive. The number of hub operators will decrease over time, and their ability to collude and block participants will grow. We aim to have at least 100 hubs across many geographies to ensure decentralization. To achieve this, hubs must be operable on affordable commodity cloud hardware, which has at most 64 TB of attached storage.
Deltas are only permitted to contain a small amount of text, and larger payloads must be stored by reference. They must be small enough to fit into a single TCP packet. Special-purpose storage systems like IPFS can host larger payloads, and applications can hydrate them at render time.
Users can only store a finite number of deltas of each type. The protocol selects limits that will keep the size of the delta graph under 64 TB. CRDTs enforce the size limit by evicting the message in the graph with the earliest ordering. Since deltas are always totally ordered, this does not change the CRDT's guarantees.
Users are only permitted to store certain deltas for a fixed time. Expiring very old deltas allows us to reclaim space, allowing more generous limits for recent messages. This is particularly useful for noisy data types like reactions where users place a greater value on recency. Time-based expiration of deltas can be deterministic as long as hubs control for clock skew and drift.
A Hub is a replica or node in the Farcaster network that synchronizes the delta graph. It is conceptually like a blockchain node but with a different consensus mechanism. Hubs receive deltas from clients over APIs and transmit them to other hubs over a peer-to-peer protocol. They listen to updates from the Ethereum blockchain and update the identity state of the delta graph as needed. Hubs conform to standards that allow different implementations to be interoperable.
Hubs communicate with their peers over a gossip protocol implemented with libp2p. Each Hub is bootstrapped with a list of peers, which it can use to discover more peers. When a hub finds a new delta, it broadcasts it to its peers, which repeat the same process propagating the update across the network.
Gossip alone does not guarantee strong eventual consistency since deltas can go missing or arrive in the wrong order. Missing deltas happen because the UDP protocol is lossy and does not guarantee delivery or ordering. Ordering is vital for convergence because Signer CRDTs must be synchronized before other CRDTs.
Hubs employ an out-of-band protocol to deal with gossip's lossiness. Each Hub maintains a merkle-patricia trie of delta identifiers that it knows about, and gossip messages include the root of this trie. A hub that receives a gossip message adds the delta's identifier into its trie. The Hubs are out of sync if the local trie root does not match the one included in the gossip messages.
An out-of-band sync process occurs where the recipient hub compares its merkle trie with the senders. If the sender has new messages unknown to the recipient, the latter fetches them using the former's gRPC APIs. If the recipient is the one with new messages, the sync process terminates.
The sender will eventually sync back with the recipient when it receives a gossip message or after a time if no gossip message was observed. The sync process must ensure that the blockchain state is as up-to-date as possible since Signer CRDTs depend on it. Due to the signer dependencies, it must also sync Signer CRDTs before syncing any other CRDT types.
Hubs whose clocks are out of sync may have CRDTs in different states due to the time-based delta pruning. Such hubs may never converge if the network is noisy enough and the skew between clocks is large enough. CRDTs must round timestamps down to the nearest 10th second before expiring them to mitigate this problem.
Peer may be malfunctioning or malicious, and sync must succeed even under such adversarial conditions. One peer can DDOS another, inhibiting its ability to stay in sync. Peers can also drop certain messages, which causes synchronization thrash. If coordinated at a large scale, it can even result in an eclipse attack where a user's messages appear to be missing from the network.
Hubs must maintain identity key pairs used to authenticate their requests. Each Hub has exponential back-off rate limits per identity, which prevents a DDOS from overwhelming the network. They also implement a scoring system for their peers that tracks how "out of sync" a peer remains and, over time, will drop low-scoring peers.
An application is a program that people use to interact with the Farcaster network. It should control a key-pair that can sign messages and maintain a connection to a Hub to publish signed messages. Users can choose the type of application that best suits their needs and switch between them at any time.
A simple application might consist of a standalone desktop or mobile client that talks directly to a Farcaster Hub. It can publish new messages and view messages published by other fids. Such applications are self-hosted and must be instantiated with the custody address or a signer.
A more sophisticated application might add a proxy backend server that indexes data from Hubs. Indexing allows servers to implement features like search, algorithmic feeds, and spam detection that are difficult or expensive to perform on the Hub. Such applications can be self-hosted by storing keys on the client; delegated by asking users for a signer; or hosted by managing all keys including the custody address.
Farcaster is designed to be upgradeable and this section covers how changes to the protocol can be proposed, how consensus is built around those changes and how they are implemented and released. During beta, the process is lightweight to encourage community participation and rapid development cycles. As we move to finalizing the protocol on mainnet stricter controls will be put in place to ensure that the protocol remains credibly neutral and that changes are safe, well tested and have passed thorough review.
New changes can be proposed by opening up a new discussion topic in the protocol, hub or contract repositories. The community can comment and make suggestions and the core team will make the final decision on accepting changes. Major changes will also be discussed on the bi-weekly developer calls. The core team controls access to the Github repositories and accepts changes. Once approved, an issue is created and the specification changes are merged into this repository.
The Farcaster core team will work closely with Hub operators and application developers to ensure that changes land smoothly with minimal disruption to the network. Hub operators also have a veto over changes to the Hub, which they can exercise by not upgrading their version of the Hub. It is desirable for developers and operators to have this power to ensure decentralization of the network, but ideally they would never need to exercise it.
Farcaster is intended to be a long-lived protocol and built on the idea of stability without stagnation. Upgrades are designed to be regular and painless, bringing continual improvements for users and developers. Users are expected to be on the latest release soon after it comes out.
The versioning system reflects this and notably does not follow semantic versioning. Instead, the version number will be initialized to 2.0.0
and planned releases increment the minor version while unplanned releases increment the patch version. So a successful first release would bump the version to 2.1.0
while a hotfix released right after would bump it to 2.1.1
and the next planned release would bump it to 2.2.0
.
Protocol documentation in this repository will change in lockstep with contract and hub versions. Tagging and releases will follow the same structure that the Hubs employ.
Contracts that are upgradable will be updated on an as-needed basis, and we expect this to be extremely rare. Unlike Hubs, contracts do not follow any pre-determined release schedule. If a contract is not dependent on any hub changes it can be deployed at any time. If it is dependent on hub changes, the hub release must ship and the 4 week waiting period must pass before the contract can be safely deployed.
Contract versions are set to the version of the hub they depend on, or the most recent release if they are not dependent on any specific version. The version is also tracked in the Solidity class name to keep track of upgrades. So the first version would be IdRegistry_2
, while an upgrade made after Hub v2.1.1
would be called IdRegistry_2_1_1
.
Hub operate on a release train where a new version is released every 12 weeks to the Farcaster mainnet. To encourage frequent updates, Hubs are programmed to shut down 16 weeks after their release date, giving operators 4 weeks to upgrade to the latest version. The new release is also programmed to stop peering with older versions 4 weeks after its release to ensure that the network safely cuts over.
Backwards incompatible Hub changes can be introduced safely with feature flags in the release train system. The feature can be programmed to turn on after the 4 week point, when older hubs are guaranteed to be disconnected from the network. Hubs can use the Ethereum block timestamp to co-ordinate their clocks and synchronize the cut over.
Farcaster will also operate a devnet, where new builds are released every week and one or more testnets, where a build is released 4 weeks before the mainnet release. Every release to devnet, testnet and mainnet branches from main and stabilizes, allowing main to continue moving forward with changes.
gantt
title Lifecycle of a Hub Release
dateFormat YYYY-MM-DD
axisFormat Week %W
section Devnet
v2.1.0-dev-1 :b1, 2014-01-05, 1w
v2.1.0-dev-2 :b2, after b1, 1w
v2.1.0-dev-3 :b3, after b2, 1w
v2.1.0-dev-4 :b4, after b3, 1w
v2.1.0-dev-5 :b5, after b4, 1w
v2.1.0-dev-6 :b6, after b5, 1w
v2.1.0-dev-7 :b7, after b6, 1w
v2.1.0-dev-8 :b8, after b7, 1w
section Testnet
v2.1.0 :after c2, 8w
v2.1.0-test :c2, after c1, 4w
v2.0.0 :done, c1, 2014-01-05, 8w
section Mainnet
v2.1.0 :2014-03-31, 8w
v2.0.0 :done, a1, 2014-01-05 , 16w
The Farcaster protocol would not have been possible without significant contributions from Dan Romero, Shane da Silva, Sean Yu, Gavi Galloway, Paul Fletcher-Hill, Sanjay Prabhu, Sagar Dhawan, Cassandra Heart and Aditya Kulkarni.
A Signed Message is a tamper-proof and self-authenticating data format that can represent a delta operation. A message is serialized as a binary flatbuffer and is the interchange format for transmitting deltas over the Farcaster network. The specification uses an abbreviated flatbuffer-like schema to describe the data structure.
The Message
object contains the payload of the message in a single data object and also includes a hash, which is used as a unique identifier, and a signature, which is used to ensure that the message hasn't been tampered with and to establish the author of the delta.
Message {
data: MessageData; // variable length message data object
hash: [ubyte]; // 16-byte hash digest of the message payload
hash_scheme: Enumeration; // 1-byte hash scheme enum (e.g. BLAKE3)
signature: [ubyte]; // 32-byte signature of the message payload
signature_scheme: Enumeration; // 1-byte signature scheme enum (e.g. EdDSA)
signer: [ubyte]; // 20-byte address or 32-byte public key that signed the message
}
The bytes of data are first passed through a hash function denoted by the hash_scheme which must be one of:
Blake3 Scheme
- a 128-bit digest produced using BLAKE3
The bytes are then passed through a signature function specified by the signature scheme, which must be one of:
ECDSA Scheme
- a 256-bit signature from an Ethereum address, where the signer is the addressEdDSA Scheme
- a 256-bit signature from an EdDSA key pair, where the signer is the public key
A MessageData
object contains generic properties like the fid, network and timestamp which are common to all deltas. The type of delta is indicated by the type property and the MessageBody
contains properties specific to the type.
MessageData {
body: MessageBody; // variable length message body, schema depends on type
fid: [ubyte]; // variable length farcaster id of the user (max: 32 bytes)
network: Enumeration; // 1-byte enumeration indicating the farcaster network
type: Enumeration; // 1-byte enumeration indicating message type
timestamp: uint32; // 4-byte epoch timestamp in seconds
}
Messages must contain a timestamp
which is an epoch timestamp in milliseconds. The start of the epoch is Jan 1st 2021, 0:00:00 GMT and all seconds are incremented from it. Timestamps cannot be set to future values since Hubs will reject messages that are ahead of their system clock.
Messages must also specify a network value which determines which Farcaster network the message belongs to. Hubs connect to one specific network and will reject messages with other network identifiers, preventing replay attacks.
FarcasterNetwork {
Mainnet = 1,
Testnet = 2,
Devnet = 3
}
There are six types of stores on Farcaster:
Signers
- key pairs authorized to sign messages on behalf of a userCast
- public, text messages published by usersReactions
- a graph relationship between a user and an objectAmp
- a graph relationship between two usersVerifications
- proofs of ownership of assets created by a userUserData
- user metadata added by the user
MessageType and MessageBody indicate the action being performed and the payload provided along with the action. For example, the action ReactionRemove
may be selected with then requires the ReactionBody
type which has information about the reaction being removed.
MessageBody {
CastAddBody,
CastRemoveBody,
ReactionBody,
AmpBody,
VerificationAddEthAddressBody,
VerificationRemoveBody,
SignerBody,
UserDataBody
}
enum MessageType {
CastAdd = 1,
CastRemove = 2,
ReactionAdd = 3,
ReactionRemove = 4,
AmpAdd = 5,
AmpRemove = 6,
VerificationAddEthAddress = 7,
VerificationRemove = 8,
SignerAdd = 9,
SignerRemove = 10,
UserDataAdd = 11
}
Messages must be stored using an anonymous Δ-state CRDT and each delta type has its own CRDT. The rules of the CRDT ensure that a deltas can be added in a manner that is commutative, associative and idempotent while never moving causally backward. Formally, the CRDT has a state S
and a merge function merge(m, S)
which returns a new state S' >= S
.
While each CRDT has its own validations, all CRDTs must implement the following validations for a message m
:
m.signer
must be a valid signer in the Signer CRDT formessage.fid
hash(data))
must matchm.hash
, where hash is specified byhash_scheme
verify(m.data, m.signer, m.signature)
must be true, where verify is specified bysignature_scheme
m.data.timestamp
must be no more than 1 minute ahead of the system clock.m.data.fid
must be a known fid number in the ID Registry.
A lexicographical ordering of messages can be determined by comparing the values of their hashes. Assume two messages
A Signer is a an Ed255192 key-pair that can sign messages on behalf of an fid. Every message in the delta-graph must be signed by a valid signer, except for the signer itself which must be signed by a valid custody address. Signers can be added and removed by users at any time with a SignerAdd
and SignerRemove
. When a signer is removed, all messages signed by it present in other CRDT's must now be considered invalid and evicted from those CRDTs.
type SignerAddBody = {
pubKey: string; // public key of the EdDSA key pair
};
type SignerRemoveBody = {
pubKey: string; // public key of the EdDSA key pair
};
Signers also become inactive if their custody address become inactive, which happens when the user transfer their fid to another Ethereum address. Inactive signers are still considered valid for a period of 60 minutes after their custody address becomes inactive after which they are evicted. The grace period allows users to transfer their custody address and preserve their history by re-authorizing the same signers from the new address.
graph TD
Custody1([Custody Address 1]) -.-> SignerA([Signer KeyPair A])
Custody1 -.-> SignerB([Signer KeyPair B])
style Custody1 stroke-dasharray: 5 5
style SignerA stroke-dasharray: 5 5
style SignerB stroke-dasharray: 5 5
Custody2([Custody Address 2]) --> SignerA1([Signer KeyPair A])
Custody2 --> |ECDSA Signature|SignerC([Signer KeyPair C])
SignerC --> CastA[Cast]
SignerC --> |EdDSA Signature| CastB[Cast]
SignerA1 --> CastC[Cast]
SignerA1 --> CastD[Amp]
The Signer Store has a two-phase CRDT3 with an add-set and a remove-set for each custody address for an fid. It keeps track of custody addresses by subscribing to Register
and Transfer
events from the Farcaster ID Registry. When a new message m
is merged into the store it is added to the add-set or remove-set depending on the type of message. If it is an add message, it must pass the following validations:
pubKey
must match the regular expression^0x[a-fA-F0-9]{40}$
signature_scheme
andsignature
must be an ECDSA signaturesigner
must match that of a valid custody address
A conflict occurs if there exists another message n
with the same values for m.data.fid
, m.signer
and m.body.pubKey
. In such cases, the following logic is followed by the merge function:
- If the timestamps are distinct, retain the message with the highest timestamps and discard the other one.
- If the timestamps are identical, and one message is a remove and the other is an add, retain the remove and discard the add.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
The store ensures that there is a maximum of 100 signer messages per fid.
A Cast is a public message created by a user which contains some text and is displayed on their account. Casts may also contain URI's pointing to media, on-chain activity or even other casts. Casts can also be removed by the user who created them at any time.
CastAddBody {
embeds: [string];
mentions: [UserId];
parent: CastId;
text: string (required);
}
CastRemoveBody {
targetHash: string;
};
CastId {
fid: [ubyte] (required);
ts_hash: [ubyte] (required);
}
UserId {
fid: [ubyte] (required);
}
A user can mention another Farcaster user inside cast by including their fid between a % symbol block. . A user can mention two users alice and bob, who have usernames @alice and @bob and fids 123 and 456 with the cast below.
{
text: "hello %0% and %1%",
mentions: [123, 456],
...
}
A cast can be structured as replies to other casts or even non-cast objects like URL's, creating threaded conversations. An optional parent
URI allows casts to indicate their parent which can be a cast, url or on-chain event. A threaded conversation of casts is a tree and the set of casts form a series of acyclic trees.
graph TB
A([cast:0x...k8j])-->B([cast:0x...ce8])
A-->C([cast:0x...f2b])
B-->D([cast:0x...c8e])
B-->E([cast:0x...48b])
B-->F([cast:0x...231])
C-->G([cast:0x...981])
H(url:www.google.com/foo)-->I([cast:0x...14d])
H-->J([cast:0x...38d])
J-->K([cast:0x...ba4])
J-->L([cast:0x...dc1])
The Cast Store is a two-phase set CRDT which tracks add and remove cast messages. A cast add message must follow these rules:
embeds
must contain between 0 and 2 URIs, each of which can be up to 256 charactersmentions
must contain between 0 and 5 Farcaster IDsparent
, if present, must be a valid URI pointingtext
must contain <= 320 valid unicode characters
The conflict id (fid, hash)
while the $(fid, targetHash)
. If a new message m
is received that has an n
, it creates a conflict which is resolved with the following rules:
- If one message is a remove and the other is an add, retain the remove and discard the add.
- If the messages are of the same time and the timestamps are distinct, retain the one with the highest timestamp.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
The store ensures that there is a maximum of 10,000 cast messages per fid and any casts older than 1 year are expired from the set.
A Reaction is a type of link between a user and a target which can be a cast, url or on-chain activity. The two types of reactions are likes and recasts, and the protocol can be extended to support new types. Reactions can be added and removed at any time and represent an edge in the social graph. Add and remove messages for reactions have identical bodies with different types.
ReactionBody {
cast: CastId;
type: ReactionType;
}
ReactionType {
Like = 1,
Recast = 2
}
The Reaction Store is a two-phase set CRDT with last write wins semantics that stores add and remove reaction messages. Each user may store a maximum of 5,000 messages and messages may have a maximum age of 3 months.
The conflict id (fid, castId, type)
and only one reaction may exist per triple. If a new message m
is received that has n
in either set it creates a conflict. Such collisions are handled with the following rules:
- If timestamps are distinct, retain the one with the highest timestamp.
- If one message is a remove and the other is an add, retain the remove and discard the add.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
An Amp is a link between two users which indicates that one user is "boosting" the other. They can be added and removed at any time and represent an edge in the social graph. Add and remove messages for amps have identical bodies with different types.
table AmpBody {
user: UserId (required);
}
The Amp Store is a two-phase set CRDT with last write wins semantics that stores add and remove amp messages in separate sets. The store also ensures that there is a maximum of 100 messages per fid and messages may have a maximum age of 3 months.
The conflict id (fid, userId)
and only one amp may exist per tuple. If a new message m
is received that has n
in either set it creates a conflict. Such collisions are handled with the rules:
- If timestamps are distinct, retain the one with the highest timestamp.
- If one message is a remove and the other is an add, retain the remove and discard the add.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
A verification is a bi-directional, cryptographic proof of ownership between a Farcaster account and an external account. They can be used to prove ownership of Ethereum addresses, specific NFTs, social media accounts, or domain names.
A VerificationAdd message must contain an identifier for the external account and a signature from it. Each type of verification will have its own AddMessage since they may contain different types of identifiers and signatures. The conflict id
VerificationAddEthAddressBody {
address: [ubyte];
eth_signature: [ubyte]; // must be an [EIP 191 version 0x45](https://eips.ethereum.org/EIPS/eip-191) signature
block_hash: [ubyte];
}
table VerificationRemoveBody {
address: [ubyte] (required);
}
The Verification Store is a two-phase set CRDT with last write wins semantics that stores add and remove verification messages in separate sets. Each user can store a maximum of 50 messages, after which the oldest messages by timestamp-hash order are expired.
The conflict id (fid, address)
and only one verification may exist per tuple. If a new message m
is received that has n
in either set it creates a conflict. Such collisions are handled with the following rules:
- If timestamps are distinct, retain the one with the highest timestamp.
- If one message is a remove and the other is an add, retain the remove and discard the add.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
User Data stores metadata about a user like their profile picture or display name. A fixed number of user data entries are permitted by the protocol and there is no remove operation, though values can be reset to null.
UserDataBody {
type: UserDataType = 1;
value: string;
}
UserDataType {
Pfp = 1,
Display = 2,
Bio = 3,
Location = 4,
Url = 5
}
The User Data Store is a grow-only set CRDT with last write wins semantics that stores user data messages in a set. The conflict id (fid, dataType)
and only one message may exist per type. If a new message m
is received that has n
in either set it creates a conflict. Such collisions are handled with the following rules:
- If timestamps are distinct, retain the one with the highest timestamp.
- If one message is a remove and the other is an add, retain the remove and discard the add.
- If the timestamps are identical and both messages are of the same type, retain the message with the highest lexicographical hash.
The store also ensures that there is a maximum of 100 messages per fid.
Many design choices have nuances and tradeoffs that are difficult to express tersely in the protocol docs. Long form proposal documents are usually produced to discuss these decisions which cover such details. This section includes links to such proposals whose design decisions are relevant to the current protocol.
Client should follow these rules when rendering casts:
- Match all mentions in text with
/\%[0-4]\%/gm
and for each matchn
look up correspondingfid
located atmentions[n]
- Replace all mention matches (
%<num>%
) in text with@fname
if it exists or the matched!fid
otherwise. - Hyperlink all mention matches to the user's profile page.
Clients may also send notifications to their users or render them as hyperlinks in their UI
Usernames are free to register during beta and are governed by a simple policy that prevents squatting and impersonation. The policy is manually enforced for now since it is not easy to automate and has two tenets:
- If you register an fname connected to a well-known public person or entity, your name may be deregistered. (e.g.
@google
) - If don't actively use an fname for 60+ days, your name may be de-registered at our discretion.
While on testnet, the core team will arbitrate conflicts and we expect to formalize a governance system as we approach mainnet. Human intervention is often needed to resolve reasonable conflicts. For instance, you register @elon
and Elon Musk signs up after you and wants the name. In such a case, we would ask three questions that guide the decision:
- Is the user active on Farcaster? (e.g. they've made several high quality posts in the last 60 days)
- Does the user have a reasonable claim to the name? (e.g. their name also Elon?)
- Does the user hold similar, active handles on other networks? (e.g. they own elon on twitter and elon.ens)
Footnotes
-
van der Linde, A., Leitão, J., & Preguiça, N. (2016). Δ-CRDTs: Making δ-CRDTs delta-based. Proceedings of the 2nd Workshop on the Principles and Practice of Consistency for Distributed Data. https://doi.org/10.1145/2911151.2911163 ↩
-
Bernstein, D.J., Duif, N., Lange, T. et al. High-speed high-security signatures. J Cryptogr Eng 2, 77–89 (2012). https://doi.org/10.1007/s13389-012-0027-1 ↩
-
Shapiro, Marc; Preguiça, Nuno; Baquero, Carlos; Zawirski, Marek (2011). "A Comprehensive Study of Convergent and Commutative Replicated Data Types". Rr-7506. ↩