├── .github ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc.yml ├── CONTRIBUTING.md ├── README.md ├── docs ├── OVERVIEW.md └── SPECIFICATION.md ├── images └── arch512.png ├── package.json └── yarn.lock /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Propose a Feature 4 | url: https://github.com/farcasterxyz/protocol/discussions 5 | about: New changes can be proposed in discussions, this tracker is for approved changes 6 | 7 | - name: Client Support 8 | url: https://github.com/farcasterxyz/protocol/discussions 9 | about: For support with desktop or mobile clients, reach out to the developers directly. -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # CI is run on main because new branches can only access caches from master, not previous branches. 4 | # So building on master allows new PR's to get the cache from before. 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | lint: 12 | timeout-minutes: 5 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: install node 21 | uses: actions/setup-node@v3 22 | with: 23 | cache: "yarn" 24 | node-version: "18" 25 | 26 | - name: Restore cached dependencies for Node modules 27 | id: module-cache 28 | uses: actions/cache@v2 29 | with: 30 | path: ${{ github.workspace }}/node_modules 31 | key: ${{ runner.os }}--node--${{ hashFiles('**/yarn.lock') }} 32 | 33 | - name: Install dependencies 34 | run: yarn install 35 | 36 | - name: Run prettier check 37 | run: yarn lint:check 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | semi: true 2 | trailingComma: 'es5' 3 | singleQuote: true 4 | printWidth: 120 5 | tabWidth: 2 6 | useTabs: false -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Farcaster is a community-driven protocol and we welcome contributors of all skill levels. 4 | 5 | ## Ramping Up 6 | 7 | If you're new to Farcaster we recommend joining the Telegram channels and watching the most recent developer call to get up to speed on what the community is focused on. 8 | 9 | | Medium | Description | 10 | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | 11 | | [Announcements Telegram](https://t.me/farcasterxyz) | A channel for major protocol announcements. | 12 | | [Developer Telegram] | A channel for developer Q&A. | 13 | | [Dev Call Invitation](https://calendar.google.com/calendar/u/0?cid=NjA5ZWM4Y2IwMmZiMWM2ZDYyMTkzNWM1YWNkZTRlNWExN2YxOWQ2NDU3NTA3MjQwMTk3YmJlZGFjYTQ3MjZlOEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) | Open Zoom call for developers every other Thursday at 10am PT. | 14 | | [Dev Call Agenda](https://warpcast.notion.site/b08fed5cbf884e6a80b3acc2dd0666b2?v=4b51e7442af14b48a69871299c22e288) | Agendas for upcoming and prior dev calls. | 15 | | [Dev Call Recordings](https://www.youtube.com/watch?v=lmGXWP5m1_Y&list=PL0eq1PLf6eUeZnPtyKMS6uN9I5iRIlnvq) | Recordings of prior dev calls. | 16 | 17 | ## What to Contribute 18 | 19 | No contribution is too small and we welcome your help. There's always something to work on, no matter how 20 | experienced you are. You can help improve Farcaster by: 21 | 22 | 1. Improving the quality of documentation in this repository 23 | 2. Making improvements or fixes to our [Ethereum contracts](https://github.com/farcasterxyz/contracts) 24 | 3. Making improvements or changes to our [Hub implementation](https://github.com/farcasterxyz/hub) 25 | 4. Making a [proposal](https://github.com/farcasterxyz/protocol/discussions/categories/proposals) to change the Farcaster protocol. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Farcaster Protocol 2 | 3 | ![Archway](/images/arch512.png) 4 | 5 | ## Getting Started 6 | 7 | Farcaster is a protocol for building decentralized social apps. This repository contains the technical specifications for implementing Farcaster. 8 | 9 | If you are instead looking for: 10 | 11 | 1. How to get started, check out [farcaster.xyz](https://www.farcaster.xyz). 12 | 2. Developer documentation, check out [docs.farcaster.xyz](https://docs.farcaster.xyz). 13 | 14 | ## Specifications 15 | 16 | The specification is maintained as a Markdown file. There are three important sections: 17 | 18 | 1. [Overview](/docs/OVERVIEW.md) - A high level overview of the protocol. 19 | 2. [Specification](/docs/SPECIFICATION.md) - The technical spec for implementing Farcaster. 20 | 3. [FIP Discussions](https://github.com/farcasterxyz/protocol/discussions) - A forum where new proposals to change the specification are discussed. 21 | 22 | ## Contributing 23 | 24 | To make contributions to the protocol, please see the [contributing guidelines](CONTRIBUTING.md) 25 | -------------------------------------------------------------------------------- /docs/OVERVIEW.md: -------------------------------------------------------------------------------- 1 | # Farcaster: A Decentralized Social Network 2 | 3 | ## 1. Introduction 4 | 5 | Social networks are usually run by a company that controls its users, their data, and their relationships. This model has created many successful products but inevitably tends to the same problems over time. Users and developers have restricted freedoms and become subject to unwelcome moderation and privacy violations. 6 | 7 | A [sufficiently decentralized](https://www.varunsrinivasan.com/2022/01/11/sufficient-decentralization-for-social-networks) protocol might offer a better alternative. Developers can invest in building apps without worrying about getting kicked off the network. Users can invest in their identities knowing that they own their data and can switch apps. While harder to build in the short term, aligning incentives will lead to better long-term outcomes. 8 | 9 | Prior attempts at decentralization have taken different paths with some limited success. ActivityPub chose federation, SecureScuttlebutt went the peer-to-peer route, and peepeth was blockchain-based. Farcaster borrows from these ideas and proposes a new design that uses blockchains and [conflict-free replicated data types](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) to achieve the following: 10 | 11 | 1. Decentralized, secure, and recoverable identities for users. 12 | 2. Concurrent, permissionless, and inexpensive access to data for developers. 13 | 3. Near real-time propagation of updates across the network. 14 | 15 | ## 2. Identity 16 | 17 | A user identity is a numeric identifier like `8930123` controlled by a key pair. Farcaster uses a smart contract registry on a Turing-complete blockchain to map identifiers to key pairs. Users can generate a new key pair or address and register an identity with the contract. The registry allows key rotation in case of exposure, and smart contract wallets can protect against key loss. 18 | 19 | Identifiers, known as **Farcaster IDs** or **fids**, are numeric values that are cheap, meaningless, and in unlimited supply. Users can associate human-readable names from namespaces with their fids when using apps. Separating the identity and namespace layers allows identity to remain very decentralized. Problems of trust, like squatting and impersonation, are the domain of the namespace layer. Users are free to choose the namespace they prefer or even use many at once. 20 | 21 | ## 3. Messages 22 | 23 | A message is a user action like posting an update, liking something, or updating a profile. Messages are a few kilobytes in size, containing text and metadata, and are uniquely identified by the hash of their contents. Users must store content like images and videos elsewhere and include them by reference with URLs. Messages must include a timestamp for ordering, though these are user-reported and not secure. 24 | 25 | ```mermaid 26 | flowchart TD 27 | W2(add-post
author: alice
message: Hello World!
timestamp: 1000001
hash: 0x...4448 ) 28 | W1(add-like
author: bob
target: 0x...4448
timestamp: 1000002
hash: 0x...72d5 ) 29 | style W1 text-align:left 30 | style W2 text-align:left 31 | ``` 32 | 33 | Messages must contain implicit or explicit resource ids to handle conflicts. For example, a message updating user 123's display name can include the identifier`123.display_name`. If many messages have the same identifier, the network keeps the one with the highest order. Messages are ordered by comparing timestamps and, if they are equal, comparing hashes lexicographically. 34 | 35 | Actions such as likes are reversible by adding a _remove message_ with a higher order than the _add message_. Privacy-preserving deletes are also possible for messages that add new content such as posts. A remove message is created with the hash of the _add message_, which is then dropped by the network. 36 | 37 | ```mermaid 38 | flowchart TD 39 | W3(add-post
author: alice
message: Hello World!
timestamp: 1000001
hash: 0x...4448 ) 40 | W4(remove-post
author: alice
target: 0x...4448
timestamp: 1000004
hash: 0x...8e3d ) 41 | W1(add-like
author: bob
target: 0x...4448
timestamp: 1000001
hash: 0x...72d5 ) 42 | W2(remove-like
author: bob
target: 0x...1234
timestamp: 1000003
hash: 0x...b4da ) 43 | style W1 text-align:left 44 | style W2 text-align:left 45 | style W3 text-align:left 46 | style W4 text-align:left 47 | ``` 48 | 49 | ## 4. Authentication 50 | 51 | Users add their fid to each message and sign it with their key pair, making 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. If the fid moves to a new key pair, all messages must be re-signed with the new key pair. 52 | 53 | Users can also delegate the ability to sign messages to another key pair called a signer. Applications can generate new signers, and users approve them by signing a message with their public key. This gives applications the ability to create messages on behalf of users but does not give them the ability to control the user's identity. 54 | 55 | ```mermaid 56 | graph LR 57 | Custody([Address Key Pair]) --> |Signature|SignerA1([Signer Key Pair]) 58 | SignerA1 --> |Signature|CastC[Hello World!] 59 | SignerA1 --> |Signature|CastD[It's Alice!] 60 | ``` 61 | 62 | ## 5. Message-Graph 63 | 64 | A social network is a graph of users, their content, and their relationships. The graph is initialized with the users registered in the onchain identity registry. Users can add and remove nodes and edges to the graph by creating signed messages. The data structure used to represent this network is called a message-graph, and the server that hosts it is known as a Hub. 65 | 66 | ```mermaid 67 | graph LR 68 | A(alice) -->|author| M1([Hello World!]) 69 | B(bob) --> |author| M2([Welcome!]) 70 | M2 --> |reply-to|M1 71 | B --> |likes|M1 72 | 73 | style A text-align:left 74 | style B text-align:left 75 | style M1 text-align:left 76 | style M2 text-align:left 77 | ``` 78 | 79 | Hubs need to synchronize message-graphs across thousands of instances to achieve decentralization. Fortunately, social actions tend to be independent of each other, and when conflicts occur, they are solvable with simple rules. If three different messages add and remove likes from a post, keeping the most recent one and discarding the rest is a reasonable solution. 80 | 81 | Using CRDTs to encode these rules allows message-graphs to achieve consensus without coordination. Users can send updates to many hubs via different apps, and their state will eventually converge. Each message type has a CRDT, which compares incoming messages by resource id to catch conflicts. Last-write-wins rules combined with the timestamp-hash ordering allow for deterministic conflict resolution. 82 | 83 | Message-graph CRDTs ensure that operations are commutative, associative, and idempotent while never moving causally backward. Each CRDT has a state $S$ and a merge function $merge(m, S)$ which accepts a message and returns a new state $S' >= S$. Such CRDTs are called anonymous delta-state CRDTs[^delta-state] and can sync by comparing missing messages. 84 | 85 | Users must only be able to add a limited amount of data to CRDTs. Otherwise, a Hub becomes impractical to operate affecting the network's decentralization. CRDT's solve this by imposing per-user size limits and evicting messages with the lowest order. Time limits can also be imposed to reduce network size by evicting messages with timestamps older than the cutoff. 86 | 87 | The message-graph has weaker guarantees than its children. Messages in most CRDTs are valid only if their signer is in the signer CRDT. A message-graph must first sync signer messages before attempting to sync other kinds to ensure convergence. Under the condition of ordered sync, message-graphs can promise strong eventual consistency. 88 | 89 | # 6. Applications 90 | 91 | An _application_ is a program used to interact with the Farcaster network. It can be as simple as a mobile client that talks to a Hub. Apps can also have backends that interface with Hubs, doing the heavy lifting for feeds, suggestions, and notifications. 92 | 93 | Users can choose the type of application that best suits their needs and even use many apps at once. Applications can be **self-hosted** by storing keys on the client, delegated by using a signer, or **hosted** by managing all keys server-side. 94 | 95 | ```mermaid 96 | graph LR 97 | subgraph Blockchain 98 | id[Farcaster Contracts] 99 | end 100 | 101 | subgraph Message Graph 102 | id---hub1[
Farcaster Hub
] 103 | id---hub2[
Farcaster Hub
] 104 | end 105 | 106 | hub1---app1[
Server
] 107 | hub2---app2[
Desktop Client
] 108 | hub2---app3[
Mobile Client
] 109 | 110 | subgraph App3 111 | app1-.-client1(Desktop Client) 112 | app1-.-client2(Mobile Client) 113 | end 114 | 115 | subgraph App2 116 | app2 117 | end 118 | 119 | subgraph App1 120 | app3 121 | end 122 | ``` 123 | 124 | # 7. Conclusion 125 | 126 | An astute reader will note that Farcaster lacks features common in social networks. Timestamps are unreliable, data isn't permanent and there are no mechanisms for private messaging. The eventually consistent nature of the network also means that developers are exposed to more complexity when building applications. 127 | 128 | Farcaster makes these tradeoffs to achieve a level of decentralization that puts users and developers in control. It is far easier to add features to a decentralized network than it is to try and decentralize a feature-rich network. The protocol is robust enough to build simple, practical and public social networks that people will use. 129 | 130 | # 8. Acknowledgements 131 | 132 | The Farcaster protocol would not have been possible without significant contributions from [Varun Srinivasan](https://github.com/varunsrin), [Dan Romero](https://github.com/danromero), [Shane da Silva](https://github.com/sds), [Sean Yu](https://github.com/seansu4you87), [Gavi Galloway](https://github.com/gsgalloway), [Paul Fletcher-Hill](https://github.com/pfletcherhill), [Sanjay Prabhu](https://github.com/sanjayprabhu), Sagar Dhawan, [Cassandra Heart](https://github.com/CassOnMars), [Aditya Kulkarni](https://github.com/adityapk00) and [horsefacts](https://github.com/horsefacts). 133 | 134 | [^delta-state]: 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 135 | -------------------------------------------------------------------------------- /docs/SPECIFICATION.md: -------------------------------------------------------------------------------- 1 | # Farcaster Specifications 2 | 3 | Requirements to implement a functional version of the Farcaster protocol. 4 | 5 | Version: `2023.11.15` 6 | 7 | ## Table of Contents 8 | 9 | 1. [Contracts](#1-smart-contracts) 10 | 2. [Message Specifications](#2-message-specifications) 11 | 3. [CRDT Specifications](#3-message-graph-specifications) 12 | 4. [Hub Specifications](#4-hub-specifications) 13 | 5. [Fname Specifications](#5-fname-specifications) 14 | 6. [Versioning](#6-versioning) 15 | 16 | # 1. Smart Contracts 17 | 18 | There is a set of 3 contracts that keep track of account ids (fids), keys for the fids and the storage allocated to the fids. 19 | 20 | ## 1.1 Id Registry 21 | 22 | The Id registry contract keeps track of the fids and their custody addresses. It is a simple mapping of fid to custody address. An fid is only valid if it is present in the Id registry. 23 | 24 | The [canonical Id registry contract](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b) is deployed at `0x00000000Fc6c5F01Fc30151999387Bb99A9f489b` on Optimism. 25 | 26 | ## 1.2 Key Registry 27 | 28 | The Key registry contract keeps track of valid signing keys for the fids. A signer for an fid is only valid if it is present in the Key registry for that particular fid. Only the custody address of the fid may add or remove signers for that fid. 29 | 30 | The [canonical Key registry contract](https://optimistic.etherscan.io/address/0x00000000Fc1237824fb747aBDE0FF18990E59b7e) is deployed at `0x00000000Fc1237824fb747aBDE0FF18990E59b7e` on Optimism. 31 | 32 | ## 1.3 Storage Registry 33 | 34 | The Storage registry contract keeps track of the storage allocated to each fid. The storage for an fid is denominated in integer units. Each CRDT specifies the number of messages it can store per unit. 35 | 36 | The [canonical Storage registry contract](https://optimistic.etherscan.io/address/0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D) is deployed at `0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D` on Optimism. 37 | 38 | For a message to be accepted, the fid must be registered in the Id registry, and signed with a valid signer present in the Key registry, and the fid must have enough storage allocated in the Storage registry. 39 | 40 | # 2. Message Specifications 41 | 42 | A Message is a cryptographically signed binary data object that represents a delta-operation on the Farcaster network. 43 | 44 | Messages are specified and serialized into binary form using [proto3 protobufs](https://protobuf.dev/). Specifically, serialization of messages must be performed using [ts-proto@v1.146.0](https://github.com/stephenh/ts-proto) since serialization into bytes is not consistent across all implementations. A `Message` object contains the data payload and information required to verify the message's authenticity. 45 | 46 | ```protobuf 47 | message Message { 48 | MessageData data = 1; // Contents of the message 49 | bytes hash = 2; // Hash digest of data 50 | HashScheme hash_scheme = 3; // Hash scheme that produced the hash digest 51 | bytes signature = 4; // Signature of the hash digest 52 | SignatureScheme signature_scheme = 5; // Signature scheme that produced the signature 53 | bytes signer = 6; // Public key or address of the key pair that produced the signature 54 | optional bytes data_bytes = 7; // MessageData serialized to bytes if using protobuf serialization other than ts-proto 55 | } 56 | ``` 57 | 58 | A Message `m` is considered valid only if: 59 | 60 | 1. `data` is a valid MessageData object 61 | 2. `hash` is the serialized and hashed digest of `data` and `hash_scheme` 62 | 3. `hash_scheme` is a currently valid hashing scheme 63 | 4. `signature` is the signed output of `hash` using the `signature_scheme` and the `signer` 64 | 5. `signature_scheme` is a valid scheme permitted by the MessageType 65 | 6. `signer` is a valid public key or Ethereum address used to produce the signature 66 | 7. `data_bytes` is a valid serialized MessageData object, to be set in case the ts-proto serialization of `data` does not produce the `hash`. This field is mutually exclusive with `data`. 67 | 68 | ### Hashing 69 | 70 | Messages must be hashed by serializing the `data` protobuf into bytes using ts-proto and passing the bytes through a hashing function to obtain a digest. The valid hashing schemes are: 71 | 72 | - `BLAKE3`: A 160-bit [Blake3](https://github.com/BLAKE3-team/BLAKE3-specs) hash digest. 73 | 74 | ```protobuf 75 | enum HashScheme { 76 | HASH_SCHEME_NONE = 0; 77 | HASH_SCHEME_BLAKE3 = 1; 78 | } 79 | ``` 80 | 81 | Since the protobuf serialization byte stream is not consistent across implementations, the `data_bytes` field is provided to allow for serialization using other protobuf implementations. If `data_bytes` is present, the hub will use it to verify the `hash` digest instead of serializing the `data` using ts-proto. 82 | 83 | ### Signing 84 | 85 | Messages must be signed by taking the `hash` and signing it using one of the valid signing schemes. The type of signature scheme that can be used is determined by the `MessageType`. The valid schemes are: 86 | 87 | - `ED25519`: A 512-bit [EdDSA signature](https://www.rfc-editor.org/rfc/rfc8032) for the edwards 25519 curve. 88 | - `EIP712`: A 512-bit [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data with a Farcaster domain separator. 89 | 90 | ```protobuf 91 | enum SignatureScheme { 92 | SIGNATURE_SCHEME_NONE = 0; 93 | SIGNATURE_SCHEME_ED25519 = 1; 94 | SIGNATURE_SCHEME_EIP712 = 2; 95 | } 96 | ``` 97 | 98 | #### Farcaster Domain Separator 99 | 100 | ```json 101 | { 102 | "name": "Farcaster Verify Ethereum Address", 103 | "version": "2.0.0", 104 | "salt": "0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558" 105 | } 106 | ``` 107 | 108 | ### Timestamp-Hash Ordering 109 | 110 | Messages are totally ordered by timestamp and hash. Assume two messages $m$ and $n$ with timestamps $m_t$ and $n_t$ hashes $m_h$ and $n_h$ of equal length. Ordering is determined by the following rules: 111 | 112 | 1. If $m_t$ and $n_t$ are distinct, the larger value has the highest order. 113 | 2. If $m_t$ and $n_t$ are not distinct, and $m_h$ and $n_h$ are distinct, perform a pairwise character comparison. 114 | 3. If $m_t$ and $n_t$ are not distinct, and $m_h$ and $n_h$ are not distinct, $m$ and $n$ must be the same message. 115 | 116 | A pairwise comparison of two distinct hashes $x$ and $y$ is performed by comparing the ASCII values of the characters in $x$ and $y$ in order. The hash which has a higher ASCII character value for a distinct pair has the highest order. 117 | 118 | ## 2.1 Message Data 119 | 120 | A MessageData contains the payload of the Message, which is hashed and signed to produce the message. 121 | 122 | A `MessageData` object contains generic properties like the `fid`, `network` `timestamp` and `type` along with a `body`, which varies based on the `type`. 123 | 124 | ```protobuf 125 | message MessageData { 126 | MessageType type = 1; 127 | uint64 fid = 2; 128 | uint32 timestamp = 3; 129 | FarcasterNetwork network = 4; 130 | oneof body { 131 | CastAddBody cast_add_body = 5; 132 | CastRemoveBody cast_remove_body = 6; 133 | ReactionBody reaction_body = 7; 134 | UserNameProofBody proof_body = 8; 135 | VerificationAddEthAddressBody verification_add_eth_address_body = 9; 136 | VerificationRemoveBody verification_remove_body = 10; 137 | UserDataBody user_data_body = 12; 138 | LinkBody link_body = 14; 139 | UserNameProof username_proof_body = 15; 140 | } 141 | } 142 | ``` 143 | 144 | A MessageData `data` in a Message `m` must pass the following validations: 145 | 146 | 1. `m.data.type` must be a valid MessageType. 147 | 2. `m.data.fid` must be an integer > 0. 148 | 3. `m.data.timestamp` must be a valid Farcaster epoch timestamp not more than 600 seconds ahead of the current time. 149 | 4. `m.data.network` must be a valid Network. 150 | 5. `m.data.body` must be a valid body. 151 | 152 | #### Types 153 | 154 | A MessageType defines the intent of a message and the expected payload in the body of the message. Each MessageType can have only one valid body, but a body can be associated with multiple message types. 155 | 156 | ```protobuf 157 | enum MessageType { 158 | MESSAGE_TYPE_NONE = 0; 159 | MESSAGE_TYPE_CAST_ADD = 1; // Add a new Cast 160 | MESSAGE_TYPE_CAST_REMOVE = 2; // Remove a previously added Cast 161 | MESSAGE_TYPE_REACTION_ADD = 3; // Add a Reaction to a Cast 162 | MESSAGE_TYPE_REACTION_REMOVE = 4; // Remove a Reaction previously added to a Cast 163 | MESSAGE_TYPE_LINK_ADD = 5; // Add a new Link 164 | MESSAGE_TYPE_LINK_REMOVE = 6; // Remove an existing Link 165 | MESSAGE_TYPE_VERIFICATION_ADD_ETH_ADDRESS = 7; // Add an Ethereum Address Verification 166 | MESSAGE_TYPE_VERIFICATION_REMOVE = 8; // Remove a previously added Verification 167 | MESSAGE_TYPE_USER_DATA_ADD = 11; // Add metadata about a user 168 | MESSAGE_TYPE_USERNAME_PROOF = 12; // Prove ownership of a username 169 | } 170 | ``` 171 | 172 | #### Timestamps 173 | 174 | Timestamps must be seconds since the Farcaster epoch, which began on Jan 1, 2021 00:00:00 UTC. 175 | 176 | #### Networks 177 | 178 | Message identifiers ensure that messages cannot be replayed across different networks. 179 | 180 | ```protobuf 181 | enum FarcasterNetwork { 182 | FARCASTER_NETWORK_NONE = 0; 183 | FARCASTER_NETWORK_MAINNET = 1; // Public, stable primary network 184 | FARCASTER_NETWORK_TESTNET = 2; // Public, stable test network 185 | FARCASTER_NETWORK_DEVNET = 3; // Public, unstable test network 186 | } 187 | ``` 188 | 189 | ## 2.2 Signers 190 | 191 | A _Signer_ is an Ed25519[^ed25519] key pair that applications can use to authorize messages. 192 | 193 | A user authorizes an application's Signer with a signature from their custody address currently holding their fid. The application can use the Signer to authorize Casts, Reactions and Verifications for that user. Users can revoke a Signer at any time with a signature from their custody address. 194 | 195 | ```mermaid 196 | graph TD 197 | Custody2([Custody Address]) --> SignerA1([Signer A]) 198 | Custody2 --> |ECDSA / EIP1271 Signature|SignerC([Signer B]) 199 | SignerC --> CastA[Cast] 200 | SignerC --> |EdDSA Signature| CastB[Cast] 201 | SignerA1 --> CastC[Cast] 202 | SignerA1 --> CastD[Reaction] 203 | ``` 204 | 205 | A Signer is added or removed by registering the public key of the signer to an fid with a smart contract at a well known address. Signers can only be added for the fid owned by the caller of the contract. 206 | 207 | ## 2.3 User Data 208 | 209 | A UserData message contains metadata about a user like their display name or profile picture. 210 | 211 | A UserData message can be added with a `UserDataAdd` message. It cannot be removed, but it can be set to a null value. 212 | 213 | ```protobuf 214 | message UserDataBody { 215 | UserDataType type = 1; 216 | string value = 2; 217 | } 218 | 219 | enum UserDataType { 220 | USER_DATA_TYPE_NONE = 0; 221 | USER_DATA_TYPE_PFP = 1; // Profile Picture URL 222 | USER_DATA_TYPE_DISPLAY = 2; // Display Name 223 | USER_DATA_TYPE_BIO = 3; // Bio 224 | USER_DATA_TYPE_URL = 5; // Homepage URL 225 | USER_DATA_TYPE_USERNAME = 6; // Preferred username 226 | } 227 | ``` 228 | 229 | A UserDataAddBody in a Message `m` is valid only if it passes these validations: 230 | 231 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 232 | 2. `m.data.type` must be `MESSAGE_TYPE_USER_DATA_ADD` 233 | 3. `m.data.body.type` must be a valid `UserDataType` 234 | 4. If `m.data.body.type` is `USER_DATA_TYPE_PFP`, value must be <= 256 bytes 235 | 5. If `m.data.body.type` is `USER_DATA_TYPE_DISPLAY`, value must be <= 32 bytes 236 | 6. If `m.data.body.type` is `USER_DATA_TYPE_BIO`, value must be <= 256 bytes 237 | 7. If `m.data.body.type` is `USER_DATA_TYPE_URL`, value must be <= 256 bytes 238 | 8. If `m.data.body.type` is `USER_DATA_TYPE_USERNAME`, value must map to a valid fname. 239 | 9. `m.data.body.value` must be a valid utf-8 string 240 | 241 | A username is considered valid only if the most recent event for the fid `Transfer` event with the custody address in the `to` property. If a valid username for a given fid becomes invalid, and there is a UserDataAdd message for that fid with the fname as its value, it must be revoked. The underlying username proofs are checked once per day to determine if they are still valid. 242 | 243 | ## 2.4 Casts 244 | 245 | A Cast is a public message created by a user that contains text or URIs to other resources. 246 | 247 | Casts may specify another cast as their parent, creating a threaded conversation. A thread has a root cast with no parent and reply casts whose parents are the root or its descendants. Each thread is an acyclic tree since a reply can only be created after its parent is hashed and signed. 248 | 249 | ```mermaid 250 | graph TB 251 | A([cast:0x...k8j])-->B([cast:0x...ce8]) 252 | A-->C([cast:0x...f2b]) 253 | B-->D([cast:0x...c8e]) 254 | B-->E([cast:0x...48b]) 255 | B-->F([cast:0x...231]) 256 | C-->G([cast:0x...981]) 257 | ``` 258 | 259 | A cast may mention users, but mentions are stored separately from the text property. A mention is created by adding the user's fid to the `mentions` array and its position in bytes in the text field into the `mentions_positions` array. Casts may have up to 10 mentions. The cast "🤓 @farcaster says hello" would be represented as: 260 | 261 | ```ts 262 | { 263 | text: '🤓 says hello', 264 | mentions: [1], 265 | mentionsPositions: [5], 266 | } 267 | ``` 268 | 269 | Casts are added with a `CastAdd` message and removed with a tombstone `CastRemove` message, which ensures the message cannot be re-added while obscuring the original message's contents. 270 | 271 | ```protobuf 272 | message CastAddBody { 273 | repeated string embeds_deprecated = 1; // Deprecated embeds field 274 | repeated uint64 mentions = 2; // User fids mentioned in the text 275 | oneof parent { // Optional parent of the cast 276 | CastId parent_cast_id = 3; 277 | string parent_url = 7; // Parent URL 278 | }; 279 | string text = 4; // Text of the cast 280 | repeated uint32 mentions_positions = 5; // Byte positions of the mentions in the text 281 | repeated Embed embeds = 6; // URIs or CastIds to be embedded in the cast 282 | } 283 | 284 | 285 | message CastRemoveBody { 286 | bytes target_hash = 1; // Message.hash value of the cast being removed 287 | } 288 | 289 | message CastId { 290 | uint64 fid = 1; // Fid of the cast's author 291 | bytes hash = 2; // Message.hash value of the cast 292 | } 293 | 294 | message Embed { 295 | oneof embed { 296 | string url = 1; 297 | CastId cast_id = 2; 298 | } 299 | } 300 | ``` 301 | 302 | A CastAddBody in a message `m` is valid only if it passes these validations: 303 | 304 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 305 | 2. `m.data.type` must be `MESSAGE_TYPE_CAST_ADD`. 306 | 3. `m.data.body` must be `CastAddBody` type. 307 | 4. `m.data.body.embeds_deprecated` can contain up to 2 valid UTF8 strings whose lengths are >=1 byte and <= 256 bytes if the timestamp is <= 73612800 (5/3/23 00:00 UTC). 308 | 5. `m.data.body.mentions` must contain between 0 and 10 256-bit integer values. 309 | 6. `m.data.body.parent`, if present, must be a valid CastId or a UTF8 string whose length is >= 1 byte and <= 256 bytes. 310 | 7. `m.data.body.text` must contain <= 1024 bytes and be a valid UTF8 string. 311 | 8. `m.data.body.type` must be either `CastType.CAST` for casts with text length of 0 <= length <= 320 and `CastType.LONG_CAST` for casts that are 321 <= length <= 1024 312 | 9. `m.data.body.mentions_positions` must have unique integers between 0 and length of `text` inclusive. 313 | 10. `m.data.body.mentions_positions` integers must be in ascending order and must have as many elements as `mentions`. 314 | 11. `m.data.body.embeds` can contain up to 2 embeds, each of which is a CastId or valid UTF8 string whose length is >=1 byte and <= 256bytes. 315 | 316 | A CastRemoveBody in a message `m` is valid only if it passes these validations: 317 | 318 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 319 | 2. `m.data.type` must be `MESSAGE_TYPE_CAST_REMOVE`. 320 | 3. `m.data.body.type` must be `CastRemoveBody`. 321 | 4. `m.data.body.target_hash` must be exactly 20 bytes. 322 | 323 | A CastId `c` is valid only if it passes these validations: 324 | 325 | 1. `c.fid` is an integer > 0 326 | 2. `c.hash` is exactly 20 bytes. 327 | 328 | ## 2.5 Reactions 329 | 330 | A Reaction is a relationship between a user and a cast which can be one of several types. 331 | 332 | Reactions are added with a `ReactionAdd` message and removed with a `ReactionRemove` message which shares a common body structure. 333 | 334 | ```protobuf 335 | message ReactionBody { 336 | ReactionType type = 1; // Type of reaction 337 | oneof target { 338 | CastId target_cast_id = 2; // CastId being reacted to 339 | string target_url = 3; // URL being reacted to 340 | } 341 | } 342 | 343 | /** Type of Reaction */ 344 | enum ReactionType { 345 | REACTION_TYPE_NONE = 0; 346 | REACTION_TYPE_LIKE = 1; // Like the target cast 347 | REACTION_TYPE_RECAST = 2; // Share target cast to the user's audience 348 | } 349 | ``` 350 | 351 | A Reaction message `m` must pass these validations and the validations for ReactionAdd or ReactionRemove: 352 | 353 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 354 | 2. `m.data.body` must be `ReactionBody`. 355 | 3. `m.data.body.type` must be a valid, non-zero ReactionType 356 | 4. `m.data.body.target` must be a valid CastId or a UTF8 string between 1 and 256 bytes inclusive. 357 | 358 | A ReactionAdd message `m` is valid only if it passes these validations: 359 | 360 | 1. `m.data.type` must be `MESSAGE_TYPE_REACTION_ADD` 361 | 362 | A ReactionRemove in a message `m` is valid only if it passes these validations: 363 | 364 | 1. `m.data.type` must be `MESSAGE_TYPE_REACTION_REMOVE` 365 | 366 | ## 2.6 Verifications 367 | 368 | A Verification is a cryptographic proof of ownership of an Ethereum address. 369 | 370 | A Verification requires a signed VerificationClaim produced by the Ethereum Address. The claim must be constructed with the following properties: 371 | 372 | ```ts 373 | struct VerificationClaim { 374 | BigInt fid; // Fid of the user making the claim 375 | string address; // Ethereum address signing the claim 376 | string network; // Farcaster network that the claim is meant for 377 | string blockHash; // Blockhash at which the claim was made 378 | } 379 | ``` 380 | 381 | An [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature is requested from the Ethereum address using the Farcaster domain separator. Smart contract signatures must include `chainId` in the domain separator. A Verification is then added by constructing a `VerificationAdd` message which includes the signature and can be removed with a `VerificationRemove` message. 382 | 383 | ```protobuf 384 | message VerificationAddEthAddressBody { 385 | bytes address = 1; // Ethereum address being verified 386 | bytes eth_signature = 2; // Signature produced by the user's Ethereum address 387 | bytes block_hash = 3; // Hash of the latest Ethereum block when the signature was produced 388 | uint32 verification_type = 4; // Verification type ID, EOA or contract 389 | uint32 chain_id = 5; // Chain ID of the verification claim, for contract verifications 390 | } 391 | 392 | message VerificationRemoveBody { 393 | bytes address = 1; // Address of the Verification to remove 394 | } 395 | ``` 396 | 397 | A VerificationAddEthAddressBody or VerificationRemoveBody in a message `m` is valid only if it passes these validations: 398 | 399 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 400 | 2. `m.data.type` must be `MESSAGE_TYPE_VERIFICATION_ADD_ETH_ADDRESS` or `MESSAGE_TYPE_VERIFICATION_REMOVE` 401 | 3. `m.data.body` must be `VerificationAddEthAddressBody` if `m.data.type` was `MESSAGE_TYPE_VERIFICATION_ADD_ETH_ADDRESS`. 402 | 4. `m.data.body` must be `VerificationRemoveBody` if `m.data.type` was `MESSAGE_TYPE_VERIFICATION_REMOVE`. 403 | 5. `m.data.body.address` must be exactly 20 bytes long. 404 | 6. `m.data.body.eth_signature` must be <= 256 bytes. 405 | 7. `m.data.body.eth_signature` must be a valid EIP-712 signature of the VerificationClaim (VerificationAdd only) 406 | 8. `m.data.body.block_hash` must be exactly 32 bytes long (VerificationAdd only) 407 | 9. `m.data.body.verification_type` must be `0` or `1`. 408 | 10. If `m.data.body.verification_type` is `0`: 409 | a. `m.data.body.chain_id` must be `0`. 410 | 11. If `m.data.body.verification_type` is `1`: 411 | a. `m.data.body.chain_id` must be `1` or `10`. 412 | 413 | ## 2.7 Links 414 | 415 | A Link is a relationship between two users which can be one of several types. Links are added with a `LinkAdd` message and removed with a `LinkRemove` message which shares a common body structure. 416 | 417 | ```protobuf 418 | message LinkBody { 419 | string type = 1; 420 | optional uint32 displayTimestamp = 2; // If set, clients should use this as the following create time 421 | oneof target { 422 | uint64 fid = 3; 423 | } 424 | } 425 | ``` 426 | 427 | A Link message `m` must pass these validations and the validations for LinkAdd or LinkRemove: 428 | 429 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 430 | 2. `m.data.body` must be `LinkBody`. 431 | 3. `m.data.body.type` must be ≤ 8 bytes. 432 | 4. `m.data.body.target` must be a known fid. 433 | 5. `m.data.body.displayTimestamp` must be ≤ `m.data.timestamp` 434 | 435 | A LinkAdd message `m` is valid only if it passes these validations: 436 | 437 | 1. `m.data.type` must be `MESSAGE_TYPE_LINK_ADD` 438 | 439 | A LinkRemove in a message `m` is valid only if it passes these validations: 440 | 441 | 1. `m.data.type` must be `MESSAGE_TYPE_LINK_REMOVE` 442 | 443 | ## 2.8 Username Proof 444 | 445 | ```protobuf 446 | enum UserNameType { 447 | USERNAME_TYPE_NONE = 0; 448 | USERNAME_TYPE_FNAME = 1; 449 | USERNAME_TYPE_ENS_L1 = 2; 450 | } 451 | 452 | message UserNameProofBody { 453 | uint64 timestamp = 1; 454 | bytes name = 2; 455 | bytes owner = 3; 456 | bytes signature = 4; 457 | uint64 fid = 5; 458 | UserNameType type = 6; 459 | } 460 | ``` 461 | 462 | A UsernameProof message `m` must pass these validations: 463 | 464 | 1. `m.signature_scheme` must be `SIGNATURE_SCHEME_ED25519`. 465 | 2. `m.data.body` must be `UserNameProofBody`. 466 | 3. `m.data.body.timestamp` must be ≤ 10 mins ahead of current timestamp. 467 | 4. `m.data.body.fid` must be a known fid. 468 | 469 | A UsernameProof message `m` of type `USERNAME_TYPE_FNAME` must also pass these validations: 470 | 471 | 1. `m.data.body.name` name must match the regular expression `/^[a-z0-9][a-z0-9-]{0,15}$/`. 472 | 2. `m.data.body.owner` must be the custody address of the fid. 473 | 3. `m.data.body.signature` must be a valid ECDSA signature on the EIP-712 Username Proof message from the owner or the public key of the fname server. 474 | 475 | A UsernameProof message `m` of type `USERNAME_TYPE_ENS_L1` must also pass these validations: 476 | 477 | 1. `m.data.body.name` name must: 478 | 1. be a valid, unexpired ENS name 479 | 2. match the regular expression `/^[a-z0-9][a-z0-9-]{0,15}\.eth$/` 480 | 2. `m.data.body.owner` must: 481 | 1. be the custody address or an address that the fid has a valid VerificationMessage for. 482 | 2. be the address that the ENS names resolves to. 483 | 3. `m.data.body.signature` must be a valid ECDSA signature on the EIP-712 Username Proof message from the owner of the ENS name. 484 | 485 | # 3. Message-Graph Specifications 486 | 487 | A message-graph is a data structure that allows state to be updated concurrently without requiring a central authority to resolve conflicts. It consists of a series of anonymous Δ-state CRDT's, each of which governs a data type and how it can be updated. The message-graph is idempotent but because of its dependency on state, it is not commutative or associative. 488 | 489 | ## 3.1 CRDTs 490 | 491 | A CRDT must accept a message only if it passes the message validation rules described above. CRDTs may also implement additional validation rules that depend on the state of other CRDTs or the blockchain. CRDTs must also specify their own rules to detect conflicts between valid messages and have a mechanism to resolve conflicts. All CRDTs implement a form of last-write-wins using the total message ordering, and some CRDTs also add remove-wins rules. 492 | 493 | CRDTs also prune messages when they reach a certain size per user to prevent them from growing indefinitely. The sizes are measured in units per user. The number of units of storage a user has is determined by the Storage registry. When adding a message crosses the size limit, the message in the CRDT with the lowest timestamp-hash order is pruned. Pruning should be performed once every hour on the hour in UTC to minimize sync thrash between Hubs. If all storage units expire for a user, there is a 30 day grace period before hubs will prune all messages for the user. 494 | 495 | ### 3.1.1 General Rules 496 | 497 | All CRDTs must implement the following rules for validating messages: 498 | 499 | 1. Messages with an EIP-712 signature scheme are only valid if the signing Ethereum address is the owner of the fid. 500 | 2. Messages with an ED25519 signature scheme are only valid if the signing key pair is a Signer present in the Key registry for the fid and has never been removed. 501 | 3. Messages are only valid if the fid is owned by the custody address that signed the message, or the signer of the message, which is specified by the Id Registry. 502 | 503 | External actions on blockchains or in other CRDTs can cause messages to become invalid. Such actions must cause an immediate revocation of messages which are discarded from CRDTs, according to the following rules: 504 | 505 | 1. When a Signer is removed for an fid from the Key registry, all messages signed by the signer in other CRDTs should be revoked. 506 | 507 | ### 3.1.2 UserData CRDT 508 | 509 | The UserData CRDT validates and accepts UserDataAdd messages. The CRDT also ensures that a UserDataAdd message `m` passes these validations: 510 | 511 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 512 | 513 | A conflict occurs if two messages have the same values for `m.data.fid` and `m.data.body.type`. Conflicts are resolved with the following rules: 514 | 515 | 1. If `m.data.timestamp` values are distinct, discard the message with the lower timestamp. 516 | 2. If `m.data.timestamp` values are identical, discard the message with the lower lexicographical order. 517 | 518 | The UserData CRDT has a per-unit size limit of 50, even though this is practically unreachable with the current schema. 519 | 520 | ### 3.1.3 Cast CRDT 521 | 522 | The Cast CRDT validates and accepts CastAdd and CastRemove messages. The CRDT also ensures that the message `m` passes these validations: 523 | 524 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 525 | 526 | A conflict occurs if there exists a CastAdd Message and a CastRemove message whose `m.hash` and `m.data.body.target_hash` are identical, or if there are two CastRemove messages whose `m.data.body.target_hash` are identical. Conflicts are resolved with the following rules: 527 | 528 | 2. If `m.data.type` is distinct, discard the CastAdd message. 529 | 1. If `m.data.type` is identical and `m.data.timestamp` values are distinct, discard the message with the lower timestamp. 530 | 1. If `m.data.timestamp` and `m.data.type` values are identical, discard the message with the lower lexicographical order. 531 | 532 | The Cast CRDT has a per-unit size limit of 5,000. 533 | 534 | ### 3.1.4 Reaction CRDT 535 | 536 | The Reaction CRDT validates and accepts ReactionAdd and ReactionRemove messages. The CRDT also ensures that the message `m` passes these validations: 537 | 538 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 539 | 540 | A conflict occurs if two messages have the same values for `m.data.fid`, `m.data.body.target` and `m.data.body.type`. Conflicts are resolved with the following rules: 541 | 542 | 1. If `m.data.timestamp` is distinct, discard the message with the lower timestamp. 543 | 2. If `m.data.timestamp` is identical and `m.data.type` is distinct, discard the ReactionAdd message. 544 | 3. If `m.data.timestamp` and `m.data.type` are identical, discard the message with the lowest lexicographical order. 545 | 546 | The Reaction CRDT has a per-unit size limit of 2,500. 547 | 548 | ### 3.1.5 Verification CRDT 549 | 550 | The Verification CRDT validates and accepts VerificationAddEthereumAddress and VerificationRemove messages. The CRDT also ensures that the message `m` passes these validations: 551 | 552 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 553 | 554 | A conflict occurs if there are two messages with the same value for `m.data.body.address`. Conflicts are resolved with the following rules: 555 | 556 | 1. If `m.data.timestamp` is distinct, discard the message with the lower timestamp. 557 | 2. If `m.data.timestamp` is identical and `m.data.type` is distinct, discard the VerificationAdd message. 558 | 3. If `m.data.timestamp` and `m.data.type` are identical, discard the message with the lowest lexicographical order. 559 | 560 | The Verification CRDT has a per-unit size limit of 25. 561 | 562 | ### 3.1.6 Link CRDT 563 | 564 | The Link CRDT validates and accepts LinkAdd and LinkRemove messages. The CRDT also ensures that the message `m` passes these validations: 565 | 566 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 567 | 568 | A conflict occurs if there are two messages with the same values for `m.data.fid`, `m.data.body.type`, `m.data.body.target`. Conflicts are resolved with the following rules: 569 | 570 | 1. If `m.data.timestamp` is distinct, discard the message with the lower timestamp. 571 | 2. If `m.data.timestamp` is identical and `m.data.type` is distinct, discard the LinkAdd message. 572 | 3. If `m.data.timestamp` and `m.data.type` are identical, discard the message with the lowest lexicographical order. 573 | 574 | The Link CRDT has a per-unit size limit of 2,500. 575 | 576 | ### 3.1.7 UsernameProof CRDT 577 | 578 | The UsernameProof CRDT validates and accepts UsernameProof messages. It must also continuously re-validate ownership of the username by running a job at 2am UTC to verify ownership of all fnames and ENS Proofs. The CRDT also ensures that a UsernameProof message m passes these validations: 579 | 580 | 1. `m.signer` must be a valid key with `Keystate.ADDED` in the `KeyRegistry` contract for `m.data.fid`. 581 | 582 | A conflict occurs if two messages that have the same value for `m.name`. Conflicts are resolved with the following rules: 583 | 584 | 1. If `m.data.timestamp` values are distinct, discard the message with the lower timestamp. 585 | 2. If `m.data.timestamp` values are identical, discard the message with the lower fid. 586 | 587 | The UsernameProof CRDT has a per-unit size limit of 5. 588 | 589 | # 4. Hub Specifications 590 | 591 | A Hub is a node in the Farcaster network that provides an eventually consistent view of network state. 592 | 593 | Hubs monitor Farcaster contracts on Ethereum to track the state of identities on the network. Hubs also maintain and synchronize CRDTs with other Hub by exchanging messages. Hubs communicate using a gossip protocol as the primary delivery mechanism with an out-of-band sync process to handle edge cases. 594 | 595 | ## 4.1 Gossip Specifications 596 | 597 | Hubs communicate using [gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) implemented with [libp2p@0.42.2](https://libp2p.io/). 598 | 599 | A hub must join the network by using libp2p to connect to a bootstrap hub, which introduces it to other peers. The gossipsub network has a simple [floodsub](https://github.com/libp2p/js-libp2p-floodsub)-like configuration with a single mesh. Hubs must subscribe to two topics: primary, which is used to broadcast messages and contact info, which is used to exchange contact information to dial hubs. The topics are specific to each network and the topics for mainnet (id: 1) are: 600 | 601 | ``` 602 | f_network_1_primary 603 | f_network_1_contact_info 604 | ``` 605 | 606 | Gossip messages are protobufs that adhere to the following schema: 607 | 608 | ```protobuf 609 | message GossipAddressInfo { 610 | string address = 1; 611 | uint32 family = 2; 612 | uint32 port = 3; 613 | string dns_name = 4; 614 | } 615 | 616 | message ContactInfoContent { 617 | GossipAddressInfo gossip_address = 1; 618 | GossipAddressInfo rpc_address = 2; 619 | repeated string excluded_hashes = 3; 620 | uint32 count = 4; 621 | string hub_version = 5; 622 | FarcasterNetwork network = 6; 623 | } 624 | 625 | message GossipMessage { 626 | oneof content { 627 | Message message = 1; 628 | ContactInfoContent contact_info_content = 3; 629 | } 630 | repeated string topics = 4; 631 | bytes peer_id = 5; 632 | GossipVersion version = 6; 633 | } 634 | ``` 635 | 636 | Hubs must ingest all messages received on the messages topic and attempt to merge them, and then rebroadcast them to other hubs. Hubs must also send out its contact information every 60 seconds on the contact_info topic. 637 | 638 | ## 4.2 Sync Specifications 639 | 640 | Hubs can download all missing messages from another hub using an expensive, out-of-band process known as diff sync. 641 | 642 | Hubs must perform a diff sync when they connect to the network to ensure that they catch up to the current state. Hubs must also periodically select a random peer and perform diff sync to ensure strong eventual consistency. Gossip alone cannot guarantee this since messages can be dropped or arrive out of order. Ordering affects consistency since non-signer deltas depend on associated signer deltas being merged before them. 643 | 644 | ### 4.2.1 Trie 645 | 646 | Hubs must maintain a [Merkle Patricia Trie](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/), which contains a Sync ID for each message in a CRDT. A Message's Sync ID is a 36-byte value that is constructed using information in the message: 647 | 648 | ``` 649 | 10 bytes: timestamp 650 | 1 byte: message type 651 | 4 bytes: fid 652 | 1 byte: crdt / set type 653 | 20 bytes: hash 654 | ``` 655 | 656 | Using timestamp-prefixed ids makes the sync trie chronologically-ordered with the rightmost branch containing the sync id of the newest message. A simplified 4-byte version of the trie with 2-byte timestamps and keys is shown below. 657 | 658 | ```mermaid 659 | graph TD 660 | HubB( ):::clear --> NodeA( ) & NodeB( ) 661 | 662 | NodeA:::ts --> NodeA1( ):::ts 663 | NodeA1 --> NodeA1-1( ):::key 664 | NodeA1 --> NodeA1-2( ):::key 665 | 666 | NodeA1-1 --> NodeA1-1-1( ):::key 667 | NodeA1-1 --> NodeA1-1-2( ):::key 668 | NodeA1-1 --> NodeA1-1-3( ):::key 669 | 670 | NodeA1-2 --> NodeA1-2-1( ):::key 671 | NodeA1-2 --> NodeA1-2-2( ):::key 672 | NodeA1-2 --> NodeA1-2-3( ):::key 673 | 674 | NodeA:::ts --> NodeA2( ):::ts 675 | NodeA2 --> NodeA2-1( ):::key 676 | 677 | NodeA2-1 --> NodeA2-1-1( ):::key 678 | NodeA2-1 --> NodeA2-1-2( ):::key 679 | NodeA2-1 --> NodeA2-1-3( ):::key 680 | 681 | NodeB:::ts --> NodeB1( ):::ts 682 | NodeB1 --> NodeB1-1( ):::key 683 | NodeB1 --> NodeB1-2( ):::key 684 | 685 | NodeB1-1 --> NodeB1-1-1( ):::key 686 | NodeB1-1 --> NodeB1-1-2( ):::key 687 | NodeB1-1 --> NodeB1-1-3( ):::key 688 | 689 | NodeB1-2 --> NodeB1-2-1( ):::key 690 | NodeB1-2 --> NodeB1-2-2( ):::key 691 | NodeB1-2 --> NodeB1-2-3( ):::key 692 | 693 | classDef ts fill:#8ecae6; 694 | classDef key fill:#FEC842; 695 | classDef clear fill:#ffffff; 696 | ``` 697 | 698 | ### 4.2.2 Algorithm 699 | 700 | Hubs can discover missing messages between sync tries by comparing _exclusion sets_, which leverages the fact that tries are chronologically ordered, with new messages usually added on the right-hand side. An exclusion node (green) is one that shares a parent with a node in the latest branch (red). Exclusion nodes at each level are combined and hashed to produce a unique exclusion value for each trie level. The set of exclusion values for all levels is the exclusion set, which is the array `[hash(2021), hash(oct, nov, dec), hash (1, 2)]` in the human-readable example trie below. 701 | 702 |
703 | 704 | ```mermaid 705 | graph TD 706 | HubB(root):::clear --> NodeE(2021) & NodeF(2022) 707 | 708 | NodeE:::excl --> NodeE4(Oct):::excl 709 | NodeE4 --> NodeE4-1(1):::clear 710 | NodeE4 --> NodeE4-2(2):::clear 711 | NodeE4 --> NodeE4-3(..):::clear 712 | 713 | NodeE:::excl --> NodeE2(nov):::excl 714 | NodeE2 --> NodeE2-1(1):::clear 715 | NodeE2 --> NodeE2-2(2):::clear 716 | NodeE2 --> NodeE2-3(..):::clear 717 | 718 | NodeE --> NodeE3(dec):::excl 719 | NodeE3 --> NodeE3-1(1):::clear 720 | NodeE3 --> NodeE3-2(2):::clear 721 | NodeE3 --> NodeE3-3(..):::clear 722 | 723 | 724 | NodeF:::edge --> NodeF3(jan):::edge 725 | NodeF3 --> NodeF3-1(1):::excl 726 | NodeF3 --> NodeF3-2(2):::excl 727 | NodeF3 --> NodeF3-3(3):::edge 728 | 729 | classDef edge fill:#FE845F; 730 | classDef excl fill:#80D096; 731 | classDef clear fill:#ffffff; 732 | ``` 733 | 734 |
735 | 736 | The point at which two tries diverge is determined in constant time by comparing exclusion sets from left to right. In the example below, the first level `hash(2022)` and the second level `hash(feb)` are identical, but the third level is not: `hash(10)` vs `hash(10, 11)`. The parent node `mar` is the divergence point of the two tries. 737 | 738 |
739 | 740 | ```mermaid 741 | graph TD 742 | HubA(root a):::clear --> NodeA(2022):::excl & NodeB(2023) 743 | NodeB:::edge --> NodeB2(feb):::excl 744 | NodeB2 --> NodeB2-1(1):::clear 745 | NodeB2 --> NodeB2-2(2):::clear 746 | NodeB --> NodeB3(mar):::edge 747 | NodeB3 --> NodeB3-1(10):::excl 748 | NodeB3 --> NodeB3-2(11):::edge 749 | 750 | HubB(root b):::clear --> NodeD(2022):::excl & NodeE(2023) 751 | NodeE:::edge --> NodeE2(feb):::excl 752 | NodeE2 --> NodeE2-1(1):::clear 753 | NodeE2 --> NodeE2-2(2):::clear 754 | NodeE --> NodeE3(mar):::edge 755 | NodeE3 --> NodeE3-1(10):::excl 756 | NodeE3 --> NodeE3-2(11):::excl 757 | NodeE3 --> NodeE3-3(12):::edge 758 | 759 | classDef edge fill:#FE845F; 760 | classDef excl fill:#80D096; 761 | classDef clear fill:#ffffff; 762 | ``` 763 | 764 |
765 | Hubs must then request the full trie under the divergent node, which must be compared to find missing branches. The branches are then converted into Sync IDs, requested from the other Hub and merged into the CRDTs. 766 | 767 | ### 4.2.3 RPC Endpoints 768 | 769 | Hubs must implement the following [gRPC](https://grpc.io/) endpoints to enable diff sync. 770 | 771 | ```protobuf 772 | service HubService { 773 | rpc GetInfo(HubInfoRequest) returns (HubInfoResponse); 774 | rpc GetAllSyncIdsByPrefix(TrieNodePrefix) returns (SyncIds); 775 | rpc GetAllMessagesBySyncIds(SyncIds) returns (MessagesResponse); 776 | rpc GetSyncMetadataByPrefix(TrieNodePrefix) returns (TrieNodeMetadataResponse); 777 | rpc GetSyncSnapshotByPrefix(TrieNodePrefix) returns (TrieNodeSnapshotResponse); 778 | } 779 | 780 | message HubInfoRequest { 781 | bool db_stats = 1; 782 | } 783 | 784 | message HubInfoResponse { 785 | string version = 1; 786 | bool is_synced = 2; 787 | string nickname = 3; 788 | string root_hash = 4; 789 | } 790 | 791 | message SyncIds { 792 | repeated bytes sync_ids = 1; 793 | } 794 | 795 | message TrieNodeMetadataResponse { 796 | bytes prefix = 1; 797 | uint64 num_messages = 2; 798 | string hash = 3; 799 | repeated TrieNodeMetadataResponse children = 4; 800 | } 801 | 802 | message TrieNodeSnapshotResponse { 803 | bytes prefix = 1; 804 | repeated string excluded_hashes = 2; 805 | uint64 num_messages = 3; 806 | string root_hash = 4; 807 | } 808 | 809 | message TrieNodePrefix { 810 | bytes prefix = 1; 811 | } 812 | ``` 813 | 814 | Hubs must also implement the following methods for client RPCs: 815 | 816 | ```protobuf 817 | service HubService { 818 | // Submit Methods 819 | rpc SubmitMessage(Message) returns (Message); 820 | 821 | // Event Methods 822 | rpc Subscribe(SubscribeRequest) returns (stream HubEvent); 823 | rpc GetEvent(EventRequest) returns (HubEvent); 824 | 825 | // Casts 826 | rpc GetCast(CastId) returns (Message); 827 | rpc GetCastsByFid(FidRequest) returns (MessagesResponse); 828 | rpc GetCastsByParent(CastsByParentRequest) returns (MessagesResponse); 829 | rpc GetCastsByMention(FidRequest) returns (MessagesResponse); 830 | 831 | // Reactions 832 | rpc GetReaction(ReactionRequest) returns (Message); 833 | rpc GetReactionsByFid(ReactionsByFidRequest) returns (MessagesResponse); 834 | rpc GetReactionsByCast(ReactionsByTargetRequest) returns (MessagesResponse); // To be deprecated 835 | rpc GetReactionsByTarget(ReactionsByTargetRequest) returns (MessagesResponse); 836 | 837 | //Links 838 | rpc GetLink(LinkRequest) returns (Message); 839 | rpc GetLinksByFid(LinksByFidRequest) returns (MessagesResponse); 840 | rpc GetLinksByTarget(LinksByTargetRequest) returns (MessagesResponse); 841 | rpc GetAllLinkMessagesByFid(FidRequest) returns (MessagesResponse); 842 | 843 | // User Data 844 | rpc GetUserData(UserDataRequest) returns (Message); 845 | rpc GetUserDataByFid(FidRequest) returns (MessagesResponse); 846 | 847 | // Verifications 848 | rpc GetVerification(VerificationRequest) returns (Message); 849 | rpc GetVerificationsByFid(FidRequest) returns (MessagesResponse); 850 | 851 | // OnChain Events 852 | rpc GetOnChainSigner(SignerRequest) returns (OnChainEvent); 853 | rpc GetOnChainSignersByFid(FidRequest) returns (OnChainEventResponse); 854 | rpc GetOnChainEvents(OnChainEventRequest) returns (OnChainEventResponse); 855 | rpc GetIdRegistryOnChainEvent(FidRequest) returns (OnChainEvent); 856 | rpc GetIdRegistryOnChainEventByAddress(IdRegistryEventByAddressRequest) returns (OnChainEvent); 857 | rpc GetCurrentStorageLimitsByFid(FidRequest) returns (StorageLimitsResponse); rpc GetFids(FidsRequest) returns (FidsResponse); 858 | rpc GetFids(FidsRequest) returns (FidsResponse); 859 | 860 | // Username Proofs 861 | rpc GetUserNameProof(UserNameProofRequest) returns (UserNameProof); 862 | rpc GetUserNameProofsByFid(FidRequest) returns (UserNameProofsResponse); 863 | 864 | // Bulk Methods 865 | rpc GetAllCastMessagesByFid(FidRequest) returns (MessagesResponse); 866 | rpc GetAllReactionMessagesByFid(FidRequest) returns (MessagesResponse); 867 | rpc GetAllVerificationMessagesByFid(FidRequest) returns (MessagesResponse); 868 | rpc GetAllSignerMessagesByFid(FidRequest) returns (MessagesResponse); 869 | rpc GetAllUserDataMessagesByFid(FidRequest) returns (MessagesResponse); 870 | } 871 | 872 | message SubscribeRequest { 873 | repeated HubEventType event_types = 1; 874 | optional uint64 from_id = 2; 875 | } 876 | 877 | message EventRequest { 878 | uint64 id = 1; 879 | } 880 | 881 | message FidRequest { 882 | uint64 fid = 1; 883 | optional uint32 page_size = 2; 884 | optional bytes page_token = 3; 885 | optional bool reverse = 4; 886 | } 887 | 888 | message FidsRequest { 889 | optional uint32 page_size = 1; 890 | optional bytes page_token = 2; 891 | optional bool reverse = 3; 892 | } 893 | 894 | message FidsResponse { 895 | repeated uint64 fids = 1; 896 | optional bytes next_page_token = 2; 897 | } 898 | 899 | message MessagesResponse { 900 | repeated Message messages = 1; 901 | optional bytes next_page_token = 2; 902 | } 903 | 904 | message CastsByParentRequest { 905 | oneof parent { 906 | CastId parent_cast_id = 1; 907 | string parent_url = 5; 908 | } 909 | optional uint32 page_size = 2; 910 | optional bytes page_token = 3; 911 | optional bool reverse = 4; 912 | } 913 | 914 | message ReactionRequest { 915 | uint64 fid = 1; 916 | ReactionType reaction_type = 2; 917 | oneof target { 918 | CastId target_cast_id = 3; 919 | string target_url = 4; 920 | } 921 | } 922 | 923 | message ReactionsByFidRequest { 924 | uint64 fid = 1; 925 | optional ReactionType reaction_type = 2; 926 | optional uint32 page_size = 3; 927 | optional bytes page_token = 4; 928 | optional bool reverse = 5; 929 | } 930 | 931 | message ReactionsByTargetRequest { 932 | oneof target { 933 | CastId target_cast_id = 1; 934 | string target_url = 6; 935 | } 936 | optional ReactionType reaction_type = 2; 937 | optional uint32 page_size = 3; 938 | optional bytes page_token = 4; 939 | optional bool reverse = 5; 940 | } 941 | 942 | message LinkRequest { 943 | uint64 fid = 1; 944 | string link_type = 2; 945 | oneof target { 946 | uint64 target_fid = 3; 947 | } 948 | } 949 | 950 | message LinksByFidRequest { 951 | uint64 fid = 1; 952 | optional string link_type = 2; 953 | optional uint32 page_size = 3; 954 | optional bytes page_token = 4; 955 | optional bool reverse = 5; 956 | } 957 | 958 | message LinksByTargetRequest { 959 | oneof target { 960 | uint64 target_fid = 1; 961 | } 962 | optional string link_type = 2; 963 | optional uint32 page_size = 3; 964 | optional bytes page_token = 4; 965 | optional bool reverse = 5; 966 | } 967 | 968 | message UserNameProofRequest { 969 | bytes name = 1; 970 | } 971 | 972 | message UserNameProofsResponse { 973 | repeated UserNameProofBody usernameProofs = 1; 974 | } 975 | 976 | message UserDataRequest { 977 | uint64 fid = 1; 978 | UserDataType user_data_type = 2; 979 | } 980 | 981 | message VerificationRequest { 982 | uint64 fid = 1; 983 | bytes address = 2; 984 | } 985 | 986 | message SignerRequest { 987 | uint64 fid = 1; 988 | bytes signer = 2; 989 | } 990 | 991 | enum OnChainEventType { 992 | EVENT_TYPE_NONE = 0; 993 | EVENT_TYPE_SIGNER = 1; 994 | EVENT_TYPE_SIGNER_MIGRATED = 2; 995 | EVENT_TYPE_ID_REGISTER = 3; 996 | EVENT_TYPE_STORAGE_RENT = 4; 997 | } 998 | 999 | message OnChainEvent { 1000 | OnChainEventType type = 1; 1001 | uint32 chain_id = 2; 1002 | uint32 block_number = 3; 1003 | bytes block_hash = 4; 1004 | uint64 block_timestamp = 5; 1005 | bytes transaction_hash = 6; 1006 | uint32 log_index = 7; 1007 | uint64 fid = 8; 1008 | oneof body { 1009 | SignerEventBody signer_event_body = 9; 1010 | SignerMigratedEventBody signer_migrated_event_body = 10; 1011 | IdRegisterEventBody id_register_event_body = 11; 1012 | StorageRentEventBody storage_rent_event_body = 12; 1013 | } 1014 | uint32 tx_index = 13; 1015 | } 1016 | 1017 | enum SignerEventType { 1018 | SIGNER_EVENT_TYPE_NONE = 0; 1019 | SIGNER_EVENT_TYPE_ADD = 1; 1020 | SIGNER_EVENT_TYPE_REMOVE = 2; 1021 | SIGNER_EVENT_TYPE_ADMIN_RESET = 3; 1022 | } 1023 | 1024 | message SignerEventBody { 1025 | bytes key = 1; 1026 | uint32 key_type = 2; 1027 | SignerEventType event_type = 3; 1028 | bytes metadata = 4; 1029 | uint32 metadata_type = 5; 1030 | } 1031 | 1032 | message SignerMigratedEventBody { 1033 | uint32 migratedAt = 1; 1034 | } 1035 | 1036 | enum IdRegisterEventType { 1037 | ID_REGISTER_EVENT_TYPE_NONE = 0; 1038 | ID_REGISTER_EVENT_TYPE_REGISTER = 1; 1039 | ID_REGISTER_EVENT_TYPE_TRANSFER = 2; 1040 | ID_REGISTER_EVENT_TYPE_CHANGE_RECOVERY = 3; 1041 | } 1042 | 1043 | message IdRegisterEventBody { 1044 | bytes to = 1; 1045 | IdRegisterEventType event_type = 2; 1046 | bytes from = 3; 1047 | bytes recovery_address = 4; 1048 | } 1049 | 1050 | message StorageRentEventBody { 1051 | bytes payer = 1; 1052 | uint32 units = 2; 1053 | uint32 expiry = 3; 1054 | } 1055 | 1056 | message OnChainEventRequest { 1057 | uint64 fid = 1; 1058 | OnChainEventType event_type = 2; 1059 | optional uint32 page_size = 3; 1060 | optional bytes page_token = 4; 1061 | optional bool reverse = 5; 1062 | } 1063 | 1064 | message OnChainEventResponse { 1065 | repeated OnChainEvent events = 1; 1066 | optional bytes next_page_token = 2; 1067 | } 1068 | 1069 | message StorageLimitsResponse { 1070 | repeated StorageLimit limits = 1; 1071 | } 1072 | 1073 | enum StoreType { 1074 | STORE_TYPE_NONE = 0; 1075 | STORE_TYPE_CASTS = 1; 1076 | STORE_TYPE_LINKS = 2; 1077 | STORE_TYPE_REACTIONS = 3; 1078 | STORE_TYPE_USER_DATA = 4; 1079 | STORE_TYPE_VERIFICATIONS = 5; 1080 | STORE_TYPE_USERNAME_PROOFS = 6; 1081 | } 1082 | 1083 | message StorageLimit { 1084 | StoreType store_type = 1; 1085 | uint64 limit = 2; 1086 | } 1087 | ``` 1088 | 1089 | # 5. Fname Specifications 1090 | 1091 | ### ENS CCIP Contract 1092 | 1093 | A CCIP [ENSIP-10](https://docs.ens.domains/ens-improvement-proposals/ensip-10-wildcard-resolution) contract will be deployed on L1 which resolves \*.fcast.id names to owner addresses. It stores the URL of the nameserver and validates signatures provided by the nameserver. This resolver will support addr record lookups only. The address of the contract is **\_\_** (to be filled on deployment). 1094 | 1095 | ### **Name Server** 1096 | 1097 | The server which resolves `*.fcast.id` names lives at `fnames.farcaster.xyz`. Fnames can be claimed by submitting an EIP-712 signed message that proves ownership of an fid that does not yet have an fname. The server also provides a method to transfer fnames to other fids by proving ownership of the fname. 1098 | 1099 | Usernames are also valid subdomains (e.g. [foo.fcast.id](http://foo.fcast.id) ) though they do not currently resolve to anything. A future upgrade to the nameserver may allow the owner to set a redirect record here. The following usernames are not available for registration, since they collide with existing subdomains — `www`, `fnames` 1100 | 1101 | **Managing Fname Ownership** 1102 | 1103 | A POST request to the `/transfers` endpoint can be made register, move or deregister a username. The request body must contain : 1104 | 1105 | ```jsx 1106 | { 1107 | "from": " // 0 for registering a new fname 1108 | "to": // 0 for unregistering an existing fname 1109 | "name": "", // fname 1110 | "timestamp": // Second resolution 1111 | "owner": "
" // ETH custody address of the non-zero "from"/"to" fid as of timestamp 1112 | "signature": "" // hex EIP-712 signature signed by the "owner" address 1113 | } 1114 | ``` 1115 | 1116 | The request is rejected unless it meets the following criteria: 1117 | 1118 | 1. The fname is owned by the “from” fid or is not owned by anyone. 1119 | 2. The “to” fid does not currently own a username. 1120 | 3. The name matches the regular expression `/^[a-z0-9][a-z0-9-]{0,15}$/`. 1121 | 4. The timestamp is ≤ current time + 1 minute (for clock skew). 1122 | 5. The owner must be 1123 | 1. the address that owns the “from” fid, if the “from” fid is not 0. 1124 | 2. the address that owns the “to” fid, if the “from” fid is 0. 1125 | 3. a privileged admin address 1126 | 6. The signature is a valid EIP-712 message from the “owner” which contains the name, timestamp and owner properties. 1127 | 7. If there exists an existing proof for the fid, the timestamp of this message must be `2419200` seconds (28 days) ahead of that timestamp to prevent abuse. i.e. an fid can only change their name once every 28 days 1128 | 1129 | The domain and types for the EIP-712 signature are described below: 1130 | 1131 | ```jsx 1132 | const domain = { 1133 | name: 'Farcaster name verification', 1134 | version: '1', 1135 | chainId: 1, 1136 | verifyingContract: '0xe3be01d99baa8db9905b33a3ca391238234b79d1', // name registry contract, will be the farcaster ENS CCIP contract later 1137 | }; 1138 | 1139 | const types = { 1140 | UserNameProof: [ 1141 | { name: 'name', type: 'string' }, 1142 | { name: 'timestamp', type: 'uint256' }, 1143 | { name: 'owner', type: 'address' }, 1144 | ], 1145 | }; 1146 | ``` 1147 | 1148 | **Verifying Fname Ownership** 1149 | 1150 | Anyone can verify that a user requested verification of a name by making a call to the server. users can make a GET request to `/transfers` which returns a paginated list of events with the following schema: 1151 | 1152 | ```jsx 1153 | { 1154 | "transfers": [ 1155 | { 1156 | "id": 1, 1157 | "from": 0, 1158 | "to": 1, 1159 | "username": "test", 1160 | "timestamp": 1686680932, 1161 | "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", 1162 | // EIP-712 signature signed by the server's key 1163 | "server_signature": "0x68a1a565f603b9966f228a38d918c12f166650749359fe41e2755fabe016026b361dd7d5f917c6f8a09241b29085fbaefffb75e443a3851be85c8b53b", 1164 | // Original user provided signature 1165 | "user_signature": "0xf603b9966f228a38d918c12f166650749359fe41e2755fabe016026b361dd7d5f917c6f8a09241b29085fbaefffb75e443a3851be85c8b53b691536d1c", 1166 | }, 1167 | // ... 1168 | ] 1169 | } 1170 | ``` 1171 | 1172 | Results can be filtered with these query string parameters: 1173 | 1174 | ```jsx 1175 | from_id= // minimum id 1176 | from_ts= // minimum timestamp 1177 | fid= // filter events by a particular fid 1178 | name= // filter events for a particular name 1179 | ``` 1180 | 1181 | **Nameserver Keypair** 1182 | 1183 | The nameserver maintains its own ECDSA keypair to counter-sign messages or perform administrative actions. The `server_signature` will be signed by this key. The public key used to perform these signers can be fetched by performing a GET on `/signer` which returns: 1184 | 1185 | ```jsx 1186 | { 1187 | "address": "" // Public address for the server's signer 1188 | } 1189 | ``` 1190 | 1191 | # 6. Versioning 1192 | 1193 | Farcaster is a long-lived protocol built on the idea of [stability without stagnation](https://doc.rust-lang.org/1.30.0/book/second-edition/appendix-07-nightly-rust.html). Upgrades are designed to be regular and painless, bringing continual improvements for users and developers. 1194 | 1195 | The protocol specification is date versioned with a non-zero leading `YYYY.MM.DD` format like `2021.3.1`. A new version of the protocol specification must be released every 6 weeks. Hot-fix releases are permitted in-between regular if necessary. 1196 | 1197 | ## 6.1 Upgrade Process 1198 | 1199 | Hubs implement a specific version of the protocol, which is advertised in their `HubInfoResponse`. 1200 | 1201 | A new version of the Hub must be released every 12 weeks that supports the latest protocol specification. The release will advertise the new version and peer with other Hubs that support the same version. It must also peer with older hubs up to 4 weeks after the version release date to ensure a transition period. Hubs must ship with a cutoff date which is set to 16 weeks after the specification release date. When the cutoff date is reached, the Hub will shut down immediately and refuse to start up. 1202 | 1203 | 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 may use the Ethereum block timestamp to coordinate their clocks and synchronize the cutover. 1204 | 1205 | [^ed25519]: 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 1206 | -------------------------------------------------------------------------------- /images/arch512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farcasterxyz/protocol/48e64b81dec992bee0728764d3743975d1f4ff08/images/arch512.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "farcaster-protocol", 3 | "version": "0.0.1", 4 | "description": "specification of the farcaster protocol", 5 | "main": "README.md", 6 | "repository": "git@github.com:farcasterxyz/protocol.git", 7 | "author": "Varun Srinivasan ", 8 | "license": "unlicensed", 9 | "devDependencies": { 10 | "prettier": "^2.7.1" 11 | }, 12 | "scripts": { 13 | "lint": "yarn prettier", 14 | "lint:check": "yarn prettier:check", 15 | "prettier": "prettier --config \"./.prettierrc.yml\" --write \"**/*.{json,md}\"", 16 | "prettier:check": "prettier --config \"./.prettierrc.yml\" --check \"**/*.{json,md}\"" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | prettier@^2.8.4: 6 | version "2.8.4" 7 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3" 8 | integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== 9 | --------------------------------------------------------------------------------