显示HN:s@:基于静态网站的去中心化社交网络
Show HN: s@: decentralized social networking over static sites

原始链接: http://satproto.org/

## s@:去中心化社交网络 s@ (发音为“sat”) 是一个建立在**静态网站**之上的去中心化社交网络协议,优先考虑**自我依赖**和小型、个人化的网络。与传统的社交媒体不同,s@ **没有中央服务器或中继**——数据直接在用户网站之间流动。 每个用户拥有自己的数据,存储为网站上的**加密 JSON 文件**。基于浏览器的客户端处理信息聚合和发布。身份通过使用用户的域名进行 **HTTPS/TLS** 验证。用户资料和公钥的发现通过其域名上的 `/satellite/` 处的 `satproto.json` 文件进行。 主要功能包括端到端加密,只有关注者才能解密内容,以及简单的关注模式——你必须关注某人才能看到他们的帖子,充当内置垃圾邮件过滤器。帖子被单独存储并进行索引以实现高效加载。该协议专为朋友之间的直接连接而设计,而不是向大量受众广播。它可以通过 **GitHub Pages** 或任何静态网站托管服务轻松部署。

这个Hacker News讨论围绕着静态网站的去中心化社交网络解决方案。Remywang分享了“s@:”,一种使用复杂密码学签名的协议。 然而,用户superkuh认为“s@:”并非真正静态,因为它依赖于程序执行。他们将其与Indieweb的Webmention进行对比,Webmention是一种更简单的方案,使用标准的HTTP POST请求来验证站点之间的链接。 Webmention可以使用基本的工具实现,例如浏览器表单、cURL,甚至简单的服务器端日志记录(superkuh在其自己的静态网站上演示了这一点)。它提供了一个完全去中心化的解决方案,无需复杂的基础设施,并且可以利用社区后端,例如webmention.io,以便更容易地接收信息。核心观点是,Webmention以比“s@:”更少的复杂性实现了去中心化。
相关文章

原文
      simple  *
      static  *  social networking
self-reliant  *

Quick start

  1. Fork this repo (see below if you need a different name from satellite)
  2. Enable GitHub Pages on your fork (deploy from the main branch).
  3. Visit the GitHub Pages URL (e.g. https://username.github.io/satellite/)

While this sample implementation uses GitHub, the protocol is agnostic to the hosting service.

Using a custom repo name: by default, the client looks for data at https://{domain}/satellite/. If you already have a satellite/ path for something else, add a satproto_root.json file to the root of your main site (e.g. the username.github.io repo) pointing to the actual repo:

{ "sat_root": "my-custom-repo" }

sAT Protocol

sAT Protocol (s@) is a decentralized social networking protocol based on static sites. Each user owns a static website storing all their data in encrypted JSON stores. A client running in the browser aggregates feeds and publishes posts. It does not rely on any servers or relays.

In plain terms, s@ is designed for you and your friends, and no one else. This applies to both the technical implementation and the user experience. At the technical level, data only moves from your own website to your friend’s browser. There are no servers (like Mastodon) or relays (like the AT Protocol) in the middle. And unlike almost all social media platform today, s@ is not designed for influencers. To see a friend’s post or to have a friend see your post, you must follow each other.

Identity

A user’s identity is their domain name. Identity is authenticated by HTTPS/TLS - fetching content from a domain proves the domain owner published it.

Discovery

A s@-enabled site exposes a discovery document at:

GET https://{domain}/satellite/satproto.json

The discovery document simply contains the protocol version and the user’s public key:

{
  "satproto_version": "0.1.0",
  "public_key": "<base64-encoded X25519 public key>"
}

By convention, the client looks under /satellite/ by default. If that path is already taken, place a satproto_root.json file at the domain root containing { "sat_root": "my-custom-repo" } — the client checks this first.

Encryption Model

All user data is stored in an encrypted JSON store. Only the user and users in the owner’s follow list can decrypt it.

Keys

  • Each user generates an X25519 keypair. The public key is published in the discovery document. The private key is stored in the browser’s localStorage.
  • A random content key (256-bit symmetric key) encrypts post data with XChaCha20-Poly1305.
  • The content key is encrypted per-follower using libsodium sealed boxes (crypto_box_seal with the follower’s X25519 public key) and stored at keys/{follower-domain}.json.

Self Key (keys/_self.json)

The user’s content key and publishing secrets (e.g. GitHub access tokens) are bundled into a single sealed box (crypto_box_seal with the user’s own public key) and stored at keys/_self.json. Only the user’s private key can open it.

This allows a user to sign back in on a new device or after clearing browser storage — they only need their domain and private key.

Key Rotation (Unfollow)

When the user unfollows someone:

  1. Generate a new content key
  2. Re-encrypt all posts with the new key
  3. Re-create key envelopes for all remaining followers
  4. Update keys/_self.json with the new content key
  5. The unfollowed user’s old key no longer decrypts anything

Decryption Flow

When Bob visits Alice’s site:

  1. Resolve Alice’s data path (via satproto_root.json or the default /satellite/)
  2. Fetch keys/bob.example.com.json
  3. Decrypt the content key using Bob’s private key (crypto_box_seal_open)
  4. Fetch posts/index.json to get the list of post IDs
  5. Fetch and decrypt individual posts from posts/{id}.json.enc (XChaCha20-Poly1305 with the content key)

Data Schema

Each post is stored as an individually encrypted file. The post index (posts/index.json) is a plaintext JSON file listing post IDs newest-first, allowing clients to lazily load only recent posts.

A post object:

{
  "id": "20260309T141500Z-a1b2",
  "author": "alice.com",
  "created_at": "2026-03-09T14:15:00Z",
  "text": "Hello, decentralized world!",
  "reply_to": null,
  "reply_to_author": null
}

Post IDs are {ISO8601-compact-UTC}-{4-hex-random}, e.g. 20260309T141500Z-a1b2. The timestamp prefix gives natural sort order; the random suffix prevents collisions.

Follow List

The follow list is stored as a plain JSON file (unencrypted, since the key envelopes already reveal follows):

GET https://{domain}/satellite/follows/index.json
{
  "follows": ["bob.example.com", "carol.example.com"]
}

Feed Aggregation

The client builds a feed by:

  1. Reading the user’s follow list
  2. For each followed user, resolving their repo path
  3. For each followed user, decrypting their posts (using the key envelope the followed user published for this user)
  4. Merging all posts, sorted by created_at descending

Replies

A reply is a post with reply_to and reply_to_author set. Replies are grouped as flat threads under the original post — nested replies (replying to a reply) are not supported; you can only reply to top-level posts.

Threads are positioned in the timeline by the original post’s created_at; replies within a thread are sorted by their own created_at ascending.

If the original post is inaccessible (e.g. the viewer doesn’t follow the author), the reply is hidden entirely. A user only sees replies from people they follow — this is the spam prevention mechanism.

Publishing

The client publishes posts by:

  1. Creating a new post with a unique ID
  2. Encrypting the post JSON with the content key
  3. Pushing the encrypted post as posts/{id}.json.enc to user’s static site (e.g. via the GitHub Contents API)
  4. Updating posts/index.json to include the new post ID

Any secrets needed for publishing (e.g. GitHub token) is encrypted in keys/_self.json (see Self Key).

Static Site Structure

{domain}/satellite/
  satproto.json             # Discovery + profile + public key
  posts/
    index.json              # Post ID list (plaintext, newest first)
    {id}.json.enc           # Individually encrypted post files
  follows/
    index.json              # Follow list (unencrypted)
  keys/
    _self.json              # Sealed box: content key + credentials (owner only)
    {domain}.json           # Sealed box: content key for follower
联系我们 contact @ memedata.com