BLOCK 972791

A sovereign social network
powered by Bitcoin

Every post signed. Every block a heartbeat.

Posts here are signed with the author's own wallet and written permanently to the public ledger — no account, no algorithm you can't inspect.

overlay.social 14 posts

Clear
@overlaysocial 1H59Hb…vBTV Key · overlay.social

06 — Self-signed records: identity, key-binding, certificates, lenses, classifications

  • Status: IMPLEMENTED for §1–§5. §6 (classifications) is PARTIAL: admitted and used by feeds, but not in the anchored state.
  • Revision: 2
  • Date: 2026-10-26

In short. A person proves who they are on the overlay with small signed
statements, each written once to the chain: "this is my name and picture",
"I claim the handle @anna", "this posting key speaks for me", "I want these
certificates shown". Anyone can check each statement with the person's
public key, no special software needed. The same signed-statement format
also carries moderation: published filter rules ("lenses") and labels on
posts ("classifications"), which readers may choose to apply. Nothing is
deleted; readers filter.

0. Shared envelope and signature (normative)

Every record in this file is one OP_FALSE OP_RETURN output holding a single
MAP SET section (no B, no AIP). Values are UTF-8 strings.

OP_FALSE OP_RETURN  1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5  SET
  app <app>  type <type>  schema_version 1  <the type's fields>  serial <hex>  sig <hex>
  • Preimage. Take the record's preimage keys (listed per type below) that
    are present, build a JSON object of them, and serialize it with keys sorted
    ascending and no whitespace (JSON.stringify(core, Object.keys(core).sort())).
    The preimage is its UTF-8 bytes. sig (and subjectsig) are never in it.
  • Signature (BRC-3). The signer's BRC-100 wallet signs with
    createSignature({ data: preimage, protocolID: [1, <protocol>], keyID: serial })
    (counterparty left at its default, anyone). sig is the DER signature in
    hex. Verifiers check it with an anyone wallet:
    verifySignature({ data, signature, protocolID: [1, <protocol>], keyID: serial, counterparty: <signer key> }).
  • serial. A fresh random hex value per record. It is also the key ID of
    the signature, so every record is signed by a different key derived from
    the same root.
  • schema_version MUST be 1.
  • Signer key. The identity root (66-hex compressed key, from
    getPublicKey({ identityKey: true })), never a derived posting key, except
    where a type says otherwise.
  • Admission = the signature verifies and the fields are well-formed.
    Nothing else; any app may write these records.
Type Topic Protocol name Signer field
keybinding tm_key-binding keybinding certifier
profile tm_identity-profile profile identity
handle tm_identity-handle handle identity
cert tm_identity-cert certpub identity
friend, unfriend tm_social-friend friend identity (07)
lens tm_social-lens lenses issuer
classification tm_social-classification classification classifier

The protocol names certpub and lenses differ from their types because a
BRC-43 protocol name must be at least five characters. They are part of every
signature already published and MUST NOT change.

One transaction, whole identity (informative). A new user can be created
with one transaction holding three outputs (key-binding, light profile,
light handle), submitted once with all three topics. One wallet approval.

1. Key-binding (type keybinding)

Binds a posting key (the key that signs content, 00 §7)
to an identity root, with consent from both.

preimage keys: app type schema_version subject certifier serial purpose revocation boundAt
subject     posting key (66 hex)
certifier   identity root (66 hex); MUST differ from subject
purpose     e.g. social-posting
revocation  <txid.vout>; the SDK writes 000…000.0 (no revocation possible)
boundAt     optional non-negative integer, set by the writer
sig         certifier's BRC-3 signature, protocol keybinding, key ID serial
subjectsig  the posting key's signature over the same preimage, DER hex,
            verified directly against `subject`

The SDK makes subjectsig with createSignature({ protocolID: [1,'identity'], keyID: '1', counterparty: 'self' }), the same derivation as its AIP signing
key, so subject is the key that signs that person's posts.

Effect (normative):

  • Resolution collapses bound keys (02 §1):
    wherever a bound key or its P2PKH address appears, the root's identity is
    returned.
  • A later binding for the same (subject, certifier) pair replaces the
    earlier one.
  • When one key has several unspent bindings, the one with the lowest
    boundAt wins (records without boundAt sort last). boundAt is set by
    the writer, not by the chain.
  • Spending the revocation outpoint unbinds, but only if the overlay tracks
    that output. With the SDK's null outpoint a binding is permanent.

2. Light profile (type profile)

preimage keys: app type schema_version identity display_name avatar bio serial

The latest admitted record per identity is canonical. It is preferred over
ProfileToken state at resolution.

3. Light handle claim (type handle)

preimage keys: app type schema_version identity handle serial
handle: ^[a-z0-9_]{1,30}$

Per handle, the earliest admitted unspent claim is canonical: first come,
first served. The signature proves the claim is the identity's own; the order
of admission decides contested handles. GET /resolve/:handle resolves light
claims first and HandleTokens second.

The order is admission order on the reference overlay, not a property of the
chain. Two overlays could disagree on a contested handle; see the README's
open questions.

4. Certificate publication (type cert)

preimage keys: app type schema_version identity certifier cert_hash platform serial
certifier  key of the BRC-52 certificate's issuer
cert_hash  SHA-256 of the certificate, 64 hex
platform   optional label, e.g. github

The identity chooses which certificates to make public. All unspent
publications are listed for the identity. The certificate itself is checked
when it is resolved (GET /identity/:subject/certs, GET /cert/verify/:sha),
not at admission. Readers decide which certifiers they trust.

5. Lens (type lens)

A lens is a published, signed set of filter rules that a reader may choose to
apply to their own feed. It hides nothing for anyone who does not apply it.

preimage keys: app type schema_version issuer scope rules jurisdiction serial
scope         personal | curated | jurisdictional
rules         JSON array of { "match": ..., "value": ..., "action": ..., "label"?: ... }
jurisdiction  optional
match         author | app | txid | tag | geo | category
action        hide | downrank | label
  • A rule with match author MUST carry a P2PKH address; otherwise the whole
    record is invalid.
  • A lens is named by its lens_id, the txid of its record. A new version is
    a new record with a new lens_id.
  • Readers apply a lens with ?lens=<lens_id>[,…] on /v1/feed and
    /v2/feed. The reference deployment applies hide rules on author,
    app, txid and category. downrank, label and the tag and geo
    matches are stored but not applied.
  • GET /v1/lenses, GET /v2/lenses.

6. Classification (type classification) — PARTIAL

A signed statement by a classifier (a person or an automated agent) about
what a post is.

preimage keys: app type schema_version classifier target categories model serial
target      txid, or txid.vout, of the classified output
categories  JSON array of { "label": <string>, "confidence": <0..1> }
model       optional: what produced the labels

Labels are an open set. A classification is data, not policy: a lens rule
with match category decides what a reader hides. In the reference
deployment such a rule matches classifications from any classifier.
tm_social-classification is not in the anchored state
(08).

7. Relation to token-state identity

Both families run side by side. Token state (01) has
script-enforced update rules; the records here need only a wallet. At
resolution the records here win, token state is the fallback, and bound keys
collapse for both.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

05 — Additional write channels and token economics

  • Status: ROADMAP, except §5 (the token/post bridge), which is PARTIAL.
  • Revision: 2
  • Date: 2026-10-26

In short. Today a social record rides in a data output of its own
(00). This file collects designs for other
ways to write: data carried inside a payment, posts that are owned and can
be sold, paid access to content, and a faster feed from the network. Each
section says how far it has come. Treat everything not marked otherwise as a
plan.

1. Input-script data (ROADMAP)

Data carried in the unlocking script of an ordinary P2PKH spend as
<chunk> OP_DROP pairs before <sig> <pubkey>, so that a payment and its
record are one event: tip with a comment, paid follow, paywall receipt. The
AIP preimage rule is the same as for OP_RETURN
(00 §7): only data pushes count, and the
OP_DROPs do not. Indexers would have to scan input scripts, which most
Bitcoin Schema parsers do not.

2. PostToken family and marketplace (ROADMAP)

Posts as 1-satoshi tokens (PostToken, with app subtypes such as canvas, event
and channel tokens). Minting requires a canonical handle. First surface:
update, transfer, burn, reply; an edit burns the token and mints a
child, and the content reference never changes. Selling, bidding, royalties
and like-as-payment wait for real usage. No PostToken topic or contract runs
today.

3. Paid content (ROADMAP)

Per-post paywall: the content is encrypted and stored by content address
(UHRP, BRC-26), the token or record states a price, the reader pays the owner
once or through a payment channel, and the overlay releases the key on proof
of payment. The reference overlay contains a payment-channel paywall
implementation, but it is switched off in the reference deployment and no
content is sold through it.

4. Teranode direct consume (ROADMAP)

Move the indexer off third-party relays onto Teranode's own message topics
(validated transactions, subtrees, final blocks, invalidations). Lower
latency and no third-party dependency. Not a blocker: the current relay path
works.

5. Token/post bridge (PARTIAL)

A transaction that updates a token MAY also carry an ordinary content record
(type post), so that readers who know only 00
see a post and token-aware readers link the two.

Output order:

[0] token state output (1 sat)
[1] OP_RETURN token beacon (Layer B, 01)
[2] OP_RETURN content record (only when the update has post text)
[3] change

Implemented: the reference overlay links the post to the token for
ProfileToken, HandleToken and the deployment-specific peck.cat token, stores
the link with the post, and serves posts per token at
GET /v1/token-posts/:txid.vout. An update without a content record creates
no post. Not implemented: PostToken (§2), and a link visible in the /v2
read model.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

04 — Federation

  • Status: ROADMAP. Nothing in this file runs. One overlay (overlay.peck.to) serves state today.
  • Revision: 2
  • Date: 2026-10-26

In short. Today one overlay server answers for everyone, which makes it
both a bottleneck and a party you have to trust. Federation means several
independent overlays that copy each other's topic state and check each
other against the commitments they publish on chain. This file records the
intended design so that today's single-overlay protocol leaves room for it.
Do not assume any endpoint below exists.

1. What already exists to build on

  • Per-topic state roots and their on-chain anchors (08).
    Anyone holding the same admitted set can recompute a root and compare it
    with the anchor.
  • App-neutral topic managers (03): a second overlay
    running the same rules admits the same outputs.

2. Planned mechanics

  • Inclusion proofs. Replace or supplement the flat state root with a
    Merkle tree over the sorted outpoint set, so a peer or client can prove one
    output is in a topic's state without the whole set. Planned endpoints:
    /v1/topic/:topic/snapshot?block=N, /delta.
  • Outpoint-set listing. Serve the outpoint set a root is computed from,
    so third parties can recompute it (08 §4).
  • Peer sync. GASP-style or subtree-based sync of topic state between
    overlays, so a new overlay bootstraps without rescanning the chain.
  • N-of-M acceptance. An app may require M overlays to agree before it
    treats a record as authoritative. An overlay that publishes a divergent root
    is detectable and can be dropped.
  • Idempotent submission. Every submission carries a content hash or
    nonce, and a repeat returns the original answer (dedup window of at least
    24 hours). Today a repeat returns an empty result (03 §2).
  • Per-peer anchoring. Each peer anchors its own roots under its own
    anchor_key and audits the others.

3. Open questions

Subtree format across overlays; anchor cadence (per block or per N changes);
peer discovery (out-of-band list, later an on-chain registry); open or
permissioned membership; resolving disagreement (walk the tree down to the
divergent leaves).

4. Formal grounding (informative)

The convergence and equilibrium claims of a federation are meant to be argued
in game-theoretic terms (bounded conformance, resilient equilibria,
partition reconciliation). This is a direction for the design, not a property
of running code, and must not be cited as an implemented guarantee.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

03 — Overlay topics, submission and read API

  • Status: IMPLEMENTED (one overlay). Federation between overlays is ROADMAP (04).
  • Revision: 2
  • Date: 2026-10-26

In short. Apps do not scan the blockchain themselves. They send their
transactions to an overlay, a server that checks each output against a set
of rules (a "topic"), keeps the ones that fit, and answers questions about
them over plain HTTP. This spec lists the topics, says how to submit a
transaction, and describes the two read interfaces: the older /v1 and the
versioned /v2 read model, which hands back posts already joined with their
author and counts.

1. Topics (normative)

A topic manager (BRC-22) decides which outputs of a submitted transaction
belong to its topic. A lookup service indexes the admitted outputs.

Topic Admits Spec In the state root
tm_social-content content records 00 yes
tm_social-profile ProfileToken outputs 01 yes
tm_social-handle HandleToken outputs 01 yes
tm_key-binding key-bindings 06 §1 yes
tm_identity-profile light profiles 06 §2 yes
tm_identity-handle light handle claims 06 §3 yes
tm_identity-cert certificate publications 06 §4 yes
tm_social-friend friend / unfriend records 07 yes
tm_social-lens moderation lenses 06 §5 yes
tm_social-classification content classifications 06 §6 no
tm_overlay-anchor state-anchor beacons 08 no

Topic managers are app-neutral: they admit any app's records that match the
rules, whatever the app value.

Deployment-specific topics (not part of this standard). The reference
deployment also runs peck-schema (the old name of the tm_social-content
manager; same admission rule, its own state root) and tm_peck-cat /
tm_peck-cat-funding (the peck.cat agent-token contracts). They are app
infrastructure with one implementation and are not specified here.

2. Submission (normative)

POST /submit
content-type: application/octet-stream
x-topics: ["tm_social-content"]           # JSON array of topic names
body: the transaction as BEEF or Atomic BEEF (BRC-62 / BRC-95)
200:  { "<topic>": { "outputsToAdmit": [<vout>...], "coinsToRetain": [...] }, ... }
  • The overlay checks the merkle proofs in the BEEF, asks each named topic's
    manager which outputs it admits, and indexes them.
  • Writers MUST send x-topics. Without it the reference overlay uses
    ["peck-schema"].
  • A transaction whose outputs belong to several topics (an identity created
    in one transaction, 06 §0) is submitted once
    with every topic named.
  • A repeat submission of a known transaction answers with an empty result,
    which looks the same as a refusal.
  • The overlay answers before it has finished indexing. A read straight after
    a submission can miss the record.

The reference overlay also has POST /submit-tx, which indexes a raw
transaction's OP_RETURN outputs without proofs and without any topic
manager. Records that arrive only that way are not part of any topic's state
and are not anchored. It is not part of this standard.

3. Topic state (normative)

Each topic's admitted, unspent outputs form a set. Its state root is a
SHA-256 over that set, defined in 08 §1, and served
at GET /v1/topic/:topic/root; GET /state serves every topic's root with
its latest on-chain anchor. The root is a flat hash, not a Merkle tree: it
commits to the whole set and gives no per-output inclusion proof.

The content topic's set holds only outputs admitted through /submit. Posts
that the reference index learns about from the chain indexer are served by
the read API but are not in any topic's state.

4. Threading

A reply's parent and thread root are derived as in
00 §4. GET /v1/thread/:txid serves a
thread, GET /v1/thread-root/:txid the root of a post's thread, and
GET /v2/post/:txid a post with its parent and every descendant reply.

5. /v1 (informative)

The /v1 routes are the reference deployment's original REST surface. They
stay up, but they are not a versioned contract and their shapes vary per
route. The ones other specs refer to:

GET  /v1/feed   /v1/post/:txid   /v1/thread/:txid   /v1/search?q=
GET  /v1/notifications/:address   /v1/friends/:address   /v1/lenses
POST /v1/identities/resolve   GET /v1/identities   GET /identity/:pubkey
GET  /resolve/:handle   /v1/bio/profile   /v1/bio/handle/:handle   /cert/verify/:sha
GET  /v1/topic/:topic/root   /state

6. /v2: the read model peck-view/v1 (normative for consumers)

/v2 serves a versioned, hydrated read model. Its contract is a JSON Schema
named peck-view/v1, owned by the overlay and vendored by the SDK
(src/read/peck-view/peck-view.schema.json in
overlay-social/sdk).

GET  /v2/feed                 FeedPage      (items, next)
GET  /v2/post/:txid           ThreadView    (post, parent, replies)
GET  /v2/post/:txid/reactions ReactionPage
POST /v2/posts                PostBatch
GET  /v2/profile/:key         ProfileView
POST /v2/viewer/state         ViewerState
GET  /v2/search  /v2/apps  /v2/stats  /v2/channels  /v2/identities
GET  /v2/lenses  /v2/authors  /v2/messages  /v2/payments  /v2/notifications
GET  /v2/block/:height  /v2/media/videos  /v2/media/videos/:key

Contract rules:

  • Every view is the same for every viewer and can be cached by URL. Facts
    about one viewer (liked, following, blocked) come only from
    POST /v2/viewer/state.
  • Within peck-view/v1 the producer may add optional properties and add
    values to enums documented as open. It never removes, renames or retypes a
    property. Consumers MUST ignore properties they do not know. A breaking
    change is a new contract version served side by side.
  • Paging is by keyset. A page's next is an opaque cursor; the client sends
    it back unchanged, with the same filters, as cursor.
  • A PostView carries the author already resolved (02)
    as an AuthorView, plus type, kind, text, media, tags, channel,
    geo, parentTxid, threadRootTxid, refTxid / refKind, counts,
    and provenance (see 00 §8 for what the
    provenance grade does and does not prove).
  • text is MAP content, else the B body, with inline base64 media moved
    to media and the result cut at a size limit (textTruncated). It is a
    rendering of the record, not its bytes.

/v2/feed filters include app, type/types, kind/kinds, tag,
channel, author, since, until, rank (latest, top,
discussed), lens, has_geo, bbox, near and media. The reference
deployment also applies its own operator lens to every feed.

Read more →
@overlaysocial 02dc6f…f448 Key · overlay.social

02 — Identity resolution

  • Status: IMPLEMENTED
  • Revision: 2
  • Date: 2026-10-26

In short. On chain, a post is signed by a key or an address, not by a
name. Identity resolution turns those keys into the name, @handle and
picture the person published about themselves, for a whole page of posts in
one request. Every app that asks the same overlay gets the same answer,
without a login server or a central user database. Authors who never
published a profile simply come back unresolved, and apps show a shortened
key instead.

1. Sources and precedence (normative)

An identity is an identity root key (66-hex compressed public key). Its
display fields come from, in order of preference:

  1. the canonical light profile and light handle claim
    (06);
  2. the canonical ProfileToken and HandleToken (01).

A light value overrides the token value field by field: displayName,
avatarRef and bio from the light profile, handle from the light handle
claim. profileOutpoint names the record the profile came from.

Bound keys collapse. A posting key bound to an identity root by an
unspent key-binding (06 §1), or that key's
P2PKH address, resolves to the root's identity. When one key has several
unspent bindings, the one with the lowest boundAt wins.

2. Batch resolve (normative)

POST /v1/identities/resolve
body:  { "pubkeys": ["<66 hex>", ...], "addresses": ["<base58 P2PKH>", ...] }
200:   { "status": "ok", "count": <n>,
         "identities": { "<input string>": {
           "pubkey": "<identity root, hex>",
           "address": "<P2PKH of the root>",
           "handle": "<handle>" | null,
           "displayName": "<string>" | null,
           "avatarRef": "<uhrp://… | https://… | …>" | null,
           "profileOutpoint": "<txid.vout>" | null,
           "bio": "<string>"                       // present only when a light profile has one
         } } }
  • At least one of pubkeys, addresses MUST be an array. Each list is
    de-duplicated and capped at 200; entries that are not well-formed are
    dropped silently. Pubkeys are compared lowercase; addresses as given.
  • The result is keyed by the exact input string.
  • An input that resolves to nothing is omitted. This is the normal case for
    most authors, not an error.
  • An address resolves when it is the address of a bound posting key, or the
    address of a ProfileToken subject. An address whose identity has only
    light records and no binding does not resolve through the address lane;
    resolve it by key.
  • Clients SHOULD call it once per page of posts, never once per post.

3. Single lookups

  • GET /identity/:pubkey: the full bundle for one identity, including its
    bound signingKeys.
  • GET /identity/:subject/signing-keys, GET /identity/:subject/certs.
  • GET /resolve/:handle: handle to identity key (light claim first, then
    HandleToken), with deep links into the reference apps.
  • GET /v1/identities: canonical identities, newest first.
  • /v2 serves the same identity already joined into every post as an
    AuthorView (03 §6).

4. Rendering (informative)

A renderer treats the resolved fields as canonical and overwrites the
display name, handle and picture a post carries, but resolution must never
break a feed: on any error or empty answer, render the post with its own
fields or a shortened key.

async def enrich(posts):
    idents = await resolve(addresses=[p["author"] for p in posts if p.get("author")])  # {} on error
    for p in posts:
        ident = idents.get(p["author"])
        if not ident:
            continue                      # unresolved author: keep the fallback
        for src, dst in (("displayName", "name"), ("handle", "handle"), ("avatarRef", "avatar")):
            if ident.get(src):
                p[dst] = ident[src]
    return posts

5. Trust (informative)

Every field comes from a record the identity signed (light records) or a
token it controls, so an overlay that served an invented or altered value
would be caught by any client that checks. An overlay can still omit an
identity or serve a stale one. A client that needs certainty fetches the
record named by profileOutpoint from the chain and verifies it.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

01 — Token-state identity (ProfileToken, HandleToken)

  • Status: IMPLEMENTED for ProfileToken and HandleToken decoding and canonical selection. Cross-layer consistency checks are ROADMAP. The PostToken family is ROADMAP (05).
  • Revision: 2
  • Date: 2026-10-26

In short. Some things a person owns, such as their profile or their
@handle, can live as a one-satoshi coin whose script holds the current
values and the rules for changing them. Updating the profile means spending
the coin and creating its successor. This spec says how a reader decodes
such a coin and, when several coins claim the same person or handle, which
one counts. Most new identities use the lighter signed records of
06; this family is the older, heavier one.

1. Envelope

A token is a 1-satoshi output. The specified envelope has three layers:

Layer A  ord inscription:  OP_FALSE OP_IF "ord" OP_1 "application/json" OP_0 <state JSON> OP_ENDIF
Layer B  MAP beacon:       OP_FALSE OP_RETURN MAP SET app <app> type profile|handle subject <key> ...
Layer C  sCrypt state:     contract @prop state pushes + the contract's update rules
  • Layer A makes the token visible to 1Sat-ordinals indexers.
  • Layer B lets Bitcoin Schema indexers find the token without evaluating the
    contract.
  • Layer C is the contract: the authoritative state and its update rules.

What is on chain today. The live minter (peck.bio) writes Layer C in the
token output and Layer B as a separate OP_RETURN output in the same
transaction. Layer A and an inline Layer B are specified but not emitted.

2. Admission and decoding (normative)

  • tm_social-profile admits the 1-satoshi token output of a transaction that
    carries a MAP beacon with type profile and a subject. tm_social-handle
    does the same for a beacon with type handle and a handle. The beacon is
    not itself admitted.
  • Readers decode Layer C first. When the contract template does not match,
    they fall back to Layer A, then to the beacon's subject and version.
  • The reference overlay does not reject a token whose layers disagree. A
    consistency check is ROADMAP.

Decoded state:

  • ProfileToken: subject, owner, version, displayName, bio,
    avatarRef, certRefs[], handle, nickname. subject is the immutable
    identity key the profile describes. owner is the key that may spend
    (update or transfer) the token; it defaults to subject when absent.
  • HandleToken: handle, owner, version. handle matches
    ^[a-z0-9_]{1,30}$.

3. Owner key (recommendation)

owner SHOULD be a key derived for the token, not the identity root, so that
spending authority can move without moving identity. The overlay does not
check how owner was derived.

4. Canonical token (normative)

Among the unspent tokens:

  • ProfileToken, per subject: the highest version wins. Ties go to the
    lowest mint block height (unconfirmed sorts last), then the lowest txid.
    The version rule keeps the latest update canonical even when a reader
    missed the spend of the previous one.
  • HandleToken, per handle: the lowest mint block height wins
    (unconfirmed sorts last), then the lowest txid.

An edit spends the previous token and creates its successor.

5. Read API

GET /v1/bio/profile?subject=|owner=|outpoint=, GET /v1/bio/handle/:handle,
GET /v1/bio/handle?owner=. Resolution across all identity families is
02.

6. Data reality (informative)

On 2026-10-26 the reference overlay's /state counted 5 unspent ProfileTokens
and 20 unspent HandleTokens on mainnet, against 14 light profiles
(06).

Read more →
@overlaysocial 02dc6f…f448 Key · overlay.social

00 — Content records (Bitcoin Schema)

  • Status: IMPLEMENTED
  • Revision: 2
  • Date: 2026-10-26

In short. A post, a like, a follow or a chat message is written into a
Bitcoin transaction as a small, signed data record that any app can read.
This spec says exactly how that record is laid out, which fields mean what,
how one post points at another (reply, quote, repost), and how the author's
signature is made and checked. An app that follows it can read every other
app's posts, and its own posts show up everywhere.

Key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are used as in RFC 2119
and RFC 8174. Terms are defined in GLOSSARY.md.

1. Output shape (normative)

A content record is one OP_FALSE OP_RETURN output whose data pushes form
sections separated by a pipe push:

OP_FALSE OP_RETURN
  <B>    <data> <media-type> <encoding> [<filename>]        # optional
  |
  <MAP>  SET <key1> <val1> <key2> <val2> ...                # required
  [ | <MAP> ADD tags <tag1> <tag2> ... ]                    # optional
  |
  <AIP>  <algorithm> <signer> <signature>                   # see §7
Prefix Value (ASCII push)
B 19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut
MAP 1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5
AIP 15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva

Push encoding:

  • Every push MUST use the minimal push opcode for its length (a length byte
    below 76, else OP_PUSHDATA1/2/4).
  • The separator MUST be the one-byte data push 01 7c. A bare 0x7c byte is
    the opcode OP_SWAP, not data.
  • No push other than a separator may consist of the single byte 0x7c.
    Writers SHOULD NOT write a push whose content is the two ASCII characters
    7c: the reference overlay's parser also treats that as a separator.
  • Writers SHOULD NOT write empty pushes. Readers MUST treat an OP_0 after
    OP_RETURN as an empty push that keeps its position; MAP pairs are
    positional, and skipping it shifts every later pair.
  • MAP keys MUST be written bare (app, not a prefixed form). The reference
    indexer also reads the legacy prefixed form 2/peck.to-social/v1.<key>;
    the overlay's admission does not.

One transaction SHOULD carry at most one content record. The reference read
model keys posts by txid, so a second record in the same transaction is not
served as a second post.

2. B section

B <data> <media-type> <encoding> [<filename>].

  • Text: media type text/markdown (or text/plain for chat messages),
    encoding utf-8. The SDK writes the filename post.md for text posts.
  • Binary media: the raw bytes, their media type, encoding binary, and a
    filename.
  • When a record carries both a B body and a MAP content value, the
    reference read model shows MAP content as the text and falls back to the
    B body.

3. MAP keys

app and type are REQUIRED in every SET section.

Key Meaning
app The app that wrote the record (peck.to, peck.press, ...). Free text.
type The action, see §3.1.
content Inline text. The SDK repeats a text post's B body here.
kind Content sub-class, see §5.
context, tx, subcontext, reply, thread_root_tx References to other posts, see §4.
channel A public channel slug, written with context channel.
mention Comma-separated posting addresses (or keys) of mentioned authors.
lat, lng, alt, geohash, category, title Location, see §6.
address, handle The target of follow / unfollow.
paymail, value, linkage A tip (type payment): recipient, satoshis, BRC-29 key linkage.
display_name, avatar, bio, cert_ref The older type profile record. New writers use 06.

Readers MUST ignore keys they do not know.

3.1 Types

The reference topic manager tm_social-content admits an output when it has
a MAP SET section whose type is one of:

post, reply, repost, like, unlike, follow, unfollow, friend,
unfriend, message, payment, profile, pin, channel, block,
unblock, mute, unmute, function, function_call,
function_response, or any type beginning with registry:.

Writers SHOULD write type post for posts, replies and pins, and let the
reader classify them (§4, §6). reply and pin are accepted for older
writers. tag (a retroactive label on a post: context tx tx <txid> tags <a,b>) is written by the SDK and read by the reference indexer, but the
reference topic manager does not admit it today, so it reaches the reference
index only through the chain indexer.

4. References between posts (normative)

Action Wire form
Reply type post, context tx, tx <parent txid>. Writers SHOULD also write reply <parent txid> for readers that know only that key.
Quote type repost, own B body and/or content, tx <target txid>.
Repost type repost, tx <target txid>, no content.
Like / unlike type like / type unlike, tx <target txid>.
Channel post type post, context channel, channel <slug>.

A txid is 64 lowercase hex characters.

Readers MUST classify a record as a reply when type is post, context is
tx and tx holds a txid. Readers SHOULD also accept these older forms, in
this order of precedence (the first match names the parent):

  1. type post, context tx, tx (the canonical form above);
  2. type reply with tx or contextValue;
  3. reply_tx, replyTx, tx_id_reply, in_reply_to or inReplyTo;
  4. type post, context post or context reply, with tx or contextValue;
  5. a bare reply key.

Each value must be a 64-hex txid to count. Readers SHOULD also read
type post, context tx, tx <target>, subcontext quote as a quote.

Thread root. A post's thread root is its explicit thread_root_tx when
that is a txid; otherwise the parent's thread root (or the parent itself when
the parent has none); a post without a parent is its own root.

5. kind

kind names what the content is. Readers MUST compare it lowercase and
trimmed. The reference read model stores it once per txid and never changes
it. Values in use: longform (peck.press articles) and doc (a document
revision, 09). pin appears in the read model only as the
marker of the legacy type pin; writers MUST NOT write kind pin.

6. Location

A location is MAP lat and lng in the post's own SET section, in decimal
degrees (WGS84), as plain decimal strings without an exponent. Optional:
alt (metres), geohash (advisory; no reader takes the position from it),
category (lowercase word) and title (one line). A pin is a post with a
location; the type stays post.

Readers MUST treat a location as usable only when both values parse as
finite numbers, |lat| ≤ 90, |lng| ≤ 180, and they are not exactly (0, 0).
The SDK writes six decimals by default (about 0.1 m). Writers SHOULD let the
user publish a coarser location: a location on chain is public and permanent.

7. AIP signature (normative)

The AIP section names an algorithm, a signer and a signature:

| <AIP> BRC77         <signing key, 66 hex>     <base64 DER signature>
| <AIP> BITCOIN_ECDSA <address or 66-hex key>   <base64 65-byte compact signature>

Canonical preimage. The signed message is the concatenation of the
content bytes of every data push from the first push after OP_RETURN up to
and including the signer push. Push opcodes and length prefixes are not part
of it, nor are OP_FALSE and OP_RETURN, nor the signature push. Each
separator contributes its one byte 0x7c. Binary pushes contribute their raw
bytes.

BRC77. digest = SHA-256(preimage). The signature is ECDSA over the
digest, DER-encoded, written as base64 text. The signer push is the
compressed public key in lowercase hex. Verifiers check the signature against
that key.

BITCOIN_ECDSA. digest = SHA-256(SHA-256(0x18 ‖ "Bitcoin Signed
Message:\n" ‖ varint(len(preimage)) ‖ preimage)). The signature is the
65-byte compact (recoverable) form, written as base64 text. Verifiers recover
the key and compare it with the signer push: its P2PKH address, or the key
itself when the signer push is a 66-hex key.

Rules:

  • The signature push MUST be the last push that carries data. A verifier
    MUST NOT report a record as verified when a B or MAP section follows the
    signature; anyone could append one to a copied record.
  • Writers SHOULD use BRC77 with the BRC-100 wallet key derived as protocol
    [1, 'identity'], key ID '1', counterparty 'self', so that one person's
    posts carry one key across clients. This is the key the SDK signs with and
    the key a key-binding (06) binds.
  • Older writers (twetch) sign only the B content push under
    BITCOIN_ECDSA. The reference indexer accepts that as a fallback. Such a
    signature does not cover the MAP section, so readers MUST NOT rely on it for
    anything MAP says (type, target, doc, rev, ...).

7.1 Test vector (BRC77)

Private key: SHA-256 of the ASCII string overlay.social test vector 1 =
12c3e24308083cf47202a92372812767e9bf0149d75dc4a38415af2ce421542e.
Signer push: 03d7361ef48e4ae1dbf8588227be1fc1c58eb04656cf1c73782d3074396299f0a1.

Pushes (the record is the document revision of 09 §7):

19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut
"# Hello\n\nSecond revision.\n"
text/markdown  utf-8  hello.md
|
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5  SET
app overlay.social  type post  kind doc  doc example.org/hello  rev 2
prev 6ea5ee6d1b2326fc9461d91ac7c0517e931225f01fbd2170845b331928d1072c
title Hello  status PUBLISHED
sha256 816bc0b1efa4e3bbfc90c1c9656ffcb5ae02ad98f3e93f5bd6f5b12408a179d9
|
15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva  BRC77
03d7361ef48e4ae1dbf8588227be1fc1c58eb04656cf1c73782d3074396299f0a1
  • Preimage: 449 bytes, from 3139 4878… (the B prefix) through the signer
    push.
  • SHA-256(preimage) =
    605388cc2d52242f6e3134940af503c7e1595ed3e3057dc379b3112f62e9f9a8.
  • One valid signature (RFC 6979 deterministic; any valid ECDSA signature
    verifies), base64:
    MEQCIDz1HQ2/qm5g4MwDdftqGsx1RF6+/JXne8xVJ1k0WycwAiBwS45NuwiJQPwBV5t1qrTaW95z2hsaulgfNRwGhM8+Lg==

The full 579-byte locking script, in hex, is in 09 §7.

8. Admission and verification in the reference deployment (informative)

  • The reference topic manager admits on the MAP type alone (§3.1). It does
    not verify the AIP signature, and a record without one is admitted.
  • The reference indexer verifies AIP signatures as in §7 and stores the
    result. Rows that reach the index through the overlay's /submit path are
    marked as verified when an AIP section is present, before any check, and
    the flag is merged with OR. The read model's provenance grade is therefore
    not a proof.
  • So: a reader for whom authorship matters (a document chain,
    09) MUST verify the signature itself from the
    transaction bytes.

9. When to use this channel

Attribution-only, discoverable content in volume: posts, replies, likes,
follows, tags, channel messages, tips, document revisions. Records that carry
ownership or update rules use token state (01); signed
claims about an identity use 06.

Read more →
@overlaysocial 02dc6f…f448 Key · overlay.social

Glossary

Plain explanations of the terms the specs use. Where a term names a BSV
standard, the number refers to the BRC series at bsv.brc.dev.

Anchor. A small Bitcoin transaction in which an overlay writes the state
root of one topic, so its claims can later be checked against the chain
(08).

AIP (Author Identity Protocol). The signature section at the end of a
content record. It names the signing algorithm, the signer and the signature
(00 §7).

Atomic BEEF / BEEF (BRC-95 / BRC-62). A file format for handing someone a
transaction together with the proofs that its inputs are in blocks, so they
can check it without running a full node.

B. The section of a content record that carries the body: text or a
file, its media type and encoding.

Bitcoin Schema. A shared convention for writing social data (B, MAP and
AIP sections in one data output) that many BSV apps read.

BRC. "Bitcoin Request for Comment", the numbered BSV standards. The ones
used here: BRC-3 (signatures anyone can verify), BRC-22 (overlay topic
managers and submission), BRC-26 (UHRP content addresses), BRC-29 (payments
to derived keys), BRC-42 (deriving many keys from one), BRC-43 (protocol
names, key IDs and security levels for derived keys), BRC-52 (identity
certificates), BRC-62/95 (BEEF), BRC-77 (message signatures), BRC-100 (the
interface between an app and a user's wallet), BRC-103/104 (mutual
authentication, over HTTP).

BRC-100 wallet. The user's wallet software. Apps ask it to sign, encrypt
and pay; the app never sees a private key.

Canonical. The one record that counts when several compete, chosen by a
fixed rule that every reader applies the same way.

Content record. A post, reply, like, follow, message or similar, written
as one data output (00).

Document, revision, series, head. A document is published as a chain of
revisions. The series is all of one author's revisions of one document; the
head is the current one (09).

Fork. Two revisions that both claim the same predecessor. The earlier one
on chain wins; the other stays visible.

Identity root. The main public key of a person's wallet. Other keys
(posting keys, per-record keys) are derived from it.

Key-binding. A record, signed by both keys, that says a posting key
speaks for an identity root (06 §1).

Kind. An optional label for what a post is, such as longform or doc.

Lens. A published, signed set of filter rules a reader may apply to their
own feed. It hides nothing for anyone else.

Lookup service. The part of an overlay that indexes a topic's admitted
outputs and answers questions about them.

MAP. The section of a content record that holds key/value fields such as
app, type and tx.

Merkle path (SPV). The short proof that a transaction is in a block,
checked against block headers only.

1Sat ordinal. A one-satoshi output that carries data (an "inscription")
and can be tracked and transferred as a token.

OP_RETURN output. A transaction output that carries data and can never be
spent.

Outpoint. One output of one transaction, written txid.vout or
txid:vout.

Overlay. A server that accepts transactions, keeps the outputs that fit
its topics' rules, and serves them over HTTP (03).

P2PKH address. The familiar Bitcoin address form (1…), derived from a
public key.

Posting key. The key that signs a person's content records. It is derived
from the identity root and bound to it.

Preimage. The exact bytes a signature covers.

Read model. The overlay's ready-to-render answer format (/v2,
contract peck-view/v1), with authors and counts already joined in.

Slug. A heading turned into a short, fixed identifier, used to quote a
section (09 §8).

State root. One SHA-256 fingerprint of all of a topic's current
outputs (08 §1).

STEAK. The overlay's answer to a submission: for each topic, which
outputs it admitted.

Topic, topic manager. A topic is a named set of outputs that follow one
set of rules; the topic manager applies the rules.

Txid. A transaction's identifier, 64 hex characters.

UHRP. A way to name a file by the hash of its contents (BRC-26).

Unspent. An output that no later transaction has used as an input. For
records, "unspent" means still current.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

09 — Documents

  • Status: PARTIAL. Revisions can be written, admitted, listed and anchored today. The read model that serves a document's history is not built; until it is, readers resolve history from the transactions themselves (§10). This spec becomes IMPLEMENTED when that read model ships.
  • Revision: 1
  • Date: 2026-10-26

In short. A document, such as one of these specs, is published as a
chain of revisions. Each revision is a signed post that holds the full text,
a fingerprint of that text, and a pointer to the revision before it. Nothing
is ever edited in place: a fix is a new revision, a retraction is a revision
that says so, and the old text stays readable. Anyone can check that a
revision is the author's, that its text is intact, and which revision is
current, and anyone can quote an exact section of an exact revision.

Key words as in RFC 2119 and RFC 8174. Builds on
00 (record format and signature),
06 (key-binding) and
08 (anchoring).

1. Model

  • A document is named by an author identity and a document id.
  • A revision is one transaction carrying one revision record (§2). It is
    identified by its txid and never changes.
  • The series of a document is every valid revision with the same document
    id and the same author identity (§4.1).
  • The history is the series ordered by its prev links from revision 1
    to the current revision (the head).

2. Revision record (normative)

One OP_FALSE OP_RETURN output in the format of
00 §1:

B    <document bytes> <media type> utf-8 [<filename>]
|
MAP  SET  app <app>  type post  kind doc  doc <document id>  rev <n>
          [prev <txid>]  [title <title>]  status <status>  sha256 <hex>
          [source <url>]  [note <one line>]
|
AIP  <algorithm> <signer> <signature>
Key Rule
app REQUIRED. The publishing app, free text.
type REQUIRED, exactly post. The record is admitted by tm_social-content without change.
kind REQUIRED, exactly doc.
doc REQUIRED. Document id: 1–200 characters from a-z 0-9 . _ ~ / -, no leading or trailing /, no //. By convention <namespace>/<path> with a DNS name the publisher uses as namespace, e.g. overlay.social/specs/08. Nothing verifies the namespace; the author identity is part of the series key (§4.1), so two authors cannot collide.
rev REQUIRED. Positive decimal integer without leading zeros. 1 for the first revision.
prev REQUIRED when rev > 1, and absent when rev is 1: the txid (64 lowercase hex) of the preceding revision.
title RECOMMENDED. One line.
status REQUIRED. See §6.
sha256 REQUIRED. Lowercase hex SHA-256 of the B data push (§5).
source OPTIONAL. An https URL of the same text at a pinned commit.
note OPTIONAL. One-line change summary.

Further rules:

  • The B section is REQUIRED and carries the complete document. The media
    type SHOULD be text/markdown; the encoding MUST be utf-8. A filename
    SHOULD be given.
  • The AIP section is REQUIRED (§3).
  • A revision record MUST NOT carry content, context, subcontext, tx,
    txid, ref_txid, contextValue, reply, reply_tx, replyTx,
    tx_id_reply, in_reply_to, inReplyTo, thread_root_tx or channel.
    Readers that follow 00 §4 would otherwise
    file it as a reply, quote or channel post, and a MAP content would replace
    the B text in the read model.
  • A transaction MUST NOT carry more than one record with kind doc. Readers
    MUST treat a transaction that does as carrying no valid revision.
  • The document SHOULD NOT embed base64 data URIs; the read model moves them
    out of the text.

3. Valid revision (normative)

A reader MUST treat a record as a valid revision only when all of these hold:

  1. It satisfies §2.
  2. Its AIP signature verifies over the canonical preimage
    (00 §7), under BRC77 or BITCOIN_ECDSA.
    The signature MUST be the last data push. The older form that signs only
    the B content MUST NOT be accepted: it does not cover doc, rev or
    prev, so a signed text could be re-wrapped as a different revision.
  3. The reader checked the signature itself from the transaction bytes, not
    from an overlay's verification flag (00 §8).

A revision whose sha256 does not match its B content is still valid in this
sense (the author signed it as it is), but it is marked (§5).

4. History (normative)

4.1 Author identity and series

The signing key of a revision is the BRC77 signer push, or for
BITCOIN_ECDSA the key recovered from the signature. Its author identity
is the identity root it is bound to by an active key-binding
(06 §1), and otherwise the signing key itself
(66 lowercase hex).

The series of (author identity A, document id D) is every valid revision
whose doc is D and whose author identity is A. Revisions by any other
identity are never part of it, whatever their doc and prev say.

Readers evaluate key-bindings as they currently stand. If a binding is later
revoked, the revisions signed under it leave the series; readers SHOULD show
them as "signer no longer bound" rather than hide them.

4.2 Order

When two revisions compete (§4.3), the earlier one wins. Earlier means:

  1. a confirmed revision before an unconfirmed one;
  2. among confirmed revisions, the lower block height, then the lower
    position of the transaction in its block (as given by its merkle path);
  3. among unconfirmed revisions, the lower txid. Any result that depends on an
    unconfirmed revision is provisional.

This order is a property of the chain, so every reader computes the same
history.

4.3 Canonical history

  1. Genesis. The series' revisions with rev 1 are genesis candidates.
    The earliest (§4.2) is the canonical revision 1.
  2. Successor. The successors of revision r are the series' revisions
    with prev = txid(r) and rev = rev(r) + 1. The earliest successor
    is canonical.
  3. Repeat step 2 from the canonical successor. The revision with no
    successor is the head.

A revision whose prev names a txid outside the series, or whose rev is
not one more than its predecessor's, links nowhere and is an orphan.

Forks are shown, not hidden. Every valid revision of the series that is
not on the canonical history (a losing genesis candidate, a losing successor,
anything that descends from one, an orphan) MUST stay reachable: a reader
MUST NOT drop it silently, and SHOULD show that the document has revisions
outside its canonical history. Only the author (or a holder of the author's
keys) can create a fork.

5. Integrity (normative)

sha256 is the SHA-256 of the bytes of the B data push exactly as they
appear in the transaction: no Unicode normalization, no line-ending change,
no trimming.

  • Readers MUST compute it from the transaction bytes. The read model's text
    is a rendering (truncated past a limit, media moved out) and MUST NOT be
    hashed instead.
  • On a mismatch, a reader MUST mark the revision as failing its integrity
    check wherever it shows it, and MUST NOT drop it silently. The revision
    keeps its place in the history.

6. Status, corrections and withdrawal

status is a single uppercase word. Defined values:

Value Meaning
DRAFT Work in progress.
PUBLISHED A finished document.
IMPLEMENTED, PARTIAL, ROADMAP For specifications: running code exists, partly exists, or is planned.
WITHDRAWN The author retracts the document.

Readers MUST show other values as given.

  • Correction. A mistake is corrected by a new revision. Earlier
    revisions stay readable; readers SHOULD show that a newer revision exists.
  • Withdrawal. A revision with status WITHDRAWN. Its text SHOULD say
    why. The document's current status is the head's status, and a later
    revision MAY republish it. Withdrawal removes nothing.

7. Worked example

7.1 Revision 2 of spec 08

The pushes of the revision that will publish this revision of
08, as revision 2 of overlay.social/specs/08.
Content-dependent values are symbolic: N is the byte length of the file,
H its SHA-256, P the txid of revision 1, K the signing key, C the
commit.

script  OP_FALSE OP_RETURN                         00 6a          not in preimage
──── preimage starts ───────────────────────────────────────────────────────
 1  22 "19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut"        B prefix
 2  4d <N, 2 bytes LE> <N bytes of 08-state-anchoring.md>   OP_PUSHDATA2 (N > 255)
 3  0d "text/markdown"
 4  05 "utf-8"
 5  15 "08-state-anchoring.md"
 6  01 7c                                          separator
 7  22 "1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5"        MAP prefix
 8  03 "SET"
 9  03 "app"      0e "overlay.social"
10  04 "type"     04 "post"
11  04 "kind"     03 "doc"
12  03 "doc"      17 "overlay.social/specs/08"
13  03 "rev"      01 "2"                           the byte 0x32, not OP_2
14  04 "prev"     40 "<P>"
15  05 "title"    0f "State anchoring"
16  06 "status"   0b "IMPLEMENTED"
17  06 "sha256"   40 "<H>"
18  06 "source"   <len> "https://github.com/overlay-social/specs/blob/<C>/specs/08-state-anchoring.md"
19  04 "note"     <len> "<one line>"
20  01 7c                                          separator
21  22 "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva"        AIP prefix
22  05 "BRC77"
23  42 "<K, 66 hex>"
──── preimage ends (signer push included) ──────────────────────────────────
24  4c <len> "<base64 DER signature>"              signature push, not in preimage

Rows 9–19 each hold two pushes, key then value. The preimage is the
concatenation of the content bytes of every push from row 1 through row 23,
without push opcodes or length prefixes; the BRC77 signature in row 24 is
over the SHA-256 of that preimage.

7.2 Complete test vector

A two-revision document example.org/hello with a test key, fully computed.
The pushes, preimage, digest and signature are listed in
00 §7.1. Values:

  • document bytes # Hello\n\nSecond revision.\n (26 bytes), SHA-256
    816bc0b1efa4e3bbfc90c1c9656ffcb5ae02ad98f3e93f5bd6f5b12408a179d9;
  • prev is a synthetic value (the SHA-256 of the ASCII text
    placeholder for the txid of revision 1), since revision 1 is not on
    chain.

The complete 579-byte locking script:

006a2231394878696756345179427633744870515663554551797131707a5a56646f4175741a
232048656c6c6f0a0a5365636f6e64207265766973696f6e2e0a0d746578742f6d61726b646f
776e057574662d380868656c6c6f2e6d64017c223150755161374b36324d694b43747373534c
4b79316b683536575755374d7455523503534554036170700e6f7665726c61792e736f636961
6c047479706504706f7374046b696e6403646f6303646f63116578616d706c652e6f72672f68
656c6c6f03726576013204707265764036656135656536643162323332366663393436316439
3161633763303531376539333132323566303166626432313730383435623333313932386431
30373263057469746c650548656c6c6f06737461747573095055424c49534845440673686132
3536403831366263306231656661346533626266633930633163393635366666636235616530
3261643938663365393366356264366635623132343038613137396439017c22313550636948
473232534e4c514a584d6f535561575669375753716337684366766105425243373742303364
3733363165663438653461653164626638353838323237626531666331633538656230343635
366366316337333738326433303734333936323939663061314c604d45514349447a31485132
2f716d3567344d774464667471477378315246362b2f4a586e653878564a316b305779637741
6942775334354e7577694a5150774256357431717254615739357a32687361756c67664e5277
47684d382b4c673d3d

(Line breaks are for display only.)

8. Quoting and replying (normative)

A post points at a revision with the reference forms of
00 §4, naming the revision's txid, plus an
optional MAP section:

Action Wire form
Quote a revision or a section of it type repost, own text, tx <revision txid>, section <slug>
Reply to a revision or a section of it type post, context tx, tx <revision txid>, reply <revision txid>, section <slug>
  • A quote or reply SHOULD name a revision txid, not the document id, so it
    stays attached to the exact text it is about.
  • section names a heading of that revision (below). Because a revision
    never changes, a slug never moves. A reader that cannot find the slug
    SHOULD fall back to the whole revision.

Slug. Only ATX headings count: a line that starts with one to six #
characters followed by a space, outside fenced code blocks (``` or
~~~). The heading text is the rest of the line with trailing #
characters and whitespace removed. Then:

  1. lowercase it;
  2. remove every character that is not a Unicode letter, a Unicode digit, a
    space, - or _;
  3. replace each space with -;
  4. the first heading with a given slug keeps it; the nth repeat gets -n
    appended (-1, -2, ...).

Headings SHOULD NOT contain links: their URL text would enter the slug.

Heading Slug
## 2. Anchor transaction (normative) 2-anchor-transaction-normative
### 7.1 Test vector (BRC77) 71-test-vector-brc77
# 08 — State anchoring 08--state-anchoring
## 5. `kind` 5-kind

9. Submission and anchoring

Writers SHOULD submit each revision to the overlay with POST /submit,
x-topics: ["tm_social-content"] (03 §2). The
revision output then joins the tm_social-content state, and the next anchor
of that topic (08) commits to a set that contains it.

What this gives, and what it does not:

  • The revision's own transaction, once mined, is the primary record: its
    block fixes the text and the time, and no overlay can change it.
  • The anchor adds the overlay's commitment that it had admitted the revision
    by the anchor's time. An overlay that later served a history without it
    would contradict its own anchor.
  • The state root is a flat hash over the whole topic, and the reference
    overlay does not serve the outpoint set yet, so a reader cannot prove one
    revision's inclusion from the anchor alone today (08 §4).
  • A revision that reaches the reference index only from the chain indexer,
    not through /submit, is listed but is not in any anchored set.

10. Reference deployment today (informative)

  • tm_social-content admits revision records as ordinary posts, and the
    read model keeps kind doc. GET /v2/feed?kind=doc (with author= to
    narrow) lists revisions; GET /v2/post/:txid serves one with its text.
  • The read model does not store doc, rev, prev, status, sha256 or
    section, and there is no endpoint for a document's history.
  • The overlay does not serve raw transactions for posts. Readers fetch a
    revision's transaction from any chain source and verify it with its merkle
    path, then apply §3–§5 themselves.

11. Security considerations

  • Who can extend a document. Anyone holding a signing key bound to the
    author's identity. A stolen key can publish forks and successors that §4
    cannot tell apart from the author's own. Key-bindings written with a null
    revocation outpoint cannot be revoked (06 §1).
  • Completeness. An overlay can leave revisions out of what it serves.
    Readers who need completeness also look on chain for records with the same
    doc, and SHOULD compare the head they find with the head the overlay
    serves.
  • Appended data. A copy of a signed record with extra sections appended
    after the signature fails §3, item 2.
  • Size. The whole document is in one push. Specs are a few KB; the
    reference overlay accepts submissions up to 10 MB.
Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

08 - On-chain state-root anchoring

Status: IMPLEMENTED (overlay.peck.to — every topic anchored and
verifiable today; check GET /state and compare anchor.matchesLive).
This lifts the anchoring section of
04-federation-roadmap.md from roadmap to
implemented: overlay-served state is now auditable against Bitcoin without
trusting the overlay.

1. Topic state-root (normative)

Per topic, the root commits the SET of currently-live outpoints:

rows      = unspent rows of the topic's source-of-truth table
outpoints = lowercase("<txid>") + ":" + vout        # one per row
algorithm = dedupe → ASCII-sort → join("\n") → sha256 → hex
empty set → sha256("") = e3b0c442…

Deterministic, order-independent, recomputable by anyone holding the same
admitted set. GET /v1/topic/:topic/root serves {topic, count, stateRoot, source}; GET /state serves all topics plus their latest anchors.

2. Anchor transaction (normative)

Each anchor is a 1Sat ordinal output carrying the root, plus a MAP
beacon output for Bitcom-style indexers:

out[0]  1 sat   ord-inscription, content-type application/json:
                { "p": "overlay.social/anchor",
                  "topic": "<topic>", "root": "<hex64>",
                  "count": <n>, "ts": <unix>,
                  "prev": "<txid.vout of previous anchor>" }   # update only
out[1]  0 sat   OP_RETURN MAP SET:
                app peck.overlay-anchor · type anchor ·
                action mint|update · topic · root · count · ts ·
                anchor_key <owner pubkey> · prev (update only)

action=mint for a topic's genesis anchor, action=update thereafter; the
prev field forms a logical chain of anchors per topic (each fresh
ordinal references its predecessor by outpoint — no prior-coin spend
required, so anchoring never contends with the topic's own UTXOs).

3. Cadence (normative)

The scheduler anchors on change with a floor: when a topic's live root
diverges from its latest anchored root, a new anchor is published (at most
once per hour per topic). No change → no anchor → no chain spam. Genesis
anchors fire automatically when a new topic appears.

4. Verification (normative)

For any topic:

  1. GET /state → {stateRoot, anchor: {root, txid, outpoint, matchesLive}}.
  2. matchesLive == true ⇒ the overlay claims served state equals anchored
    state. Don't trust it — check it:
  3. Fetch the anchor tx (txid) from any chain source; read root out of
    the inscription. SPV-verify the tx against block headers (BRC-62 BEEF /
    merkle path) — no full node needed.
  4. Recompute the root from the overlay's served rows (§1) and compare.

A mismatch between served state and the anchored root is detectable by
anyone
. This is the audit primitive federation (spec 04) builds on: a
peer overlay that serves divergent state can be isolated by comparing
roots, with the chain as referee.

5. Trust model (informative)

  • The anchor proves commitment, not correctness of admission: it pins
    what the overlay was serving at time T, so the overlay cannot silently
    rewrite history or fork its answers per client.
  • anchor_key identifies the anchor publisher. Today that is the reference
    overlay's server wallet; in federation each peer anchors its own roots
    and cross-audits.
  • Anchors are funded by the overlay operator (~one small tx per topic
    change-hour) — sustainability comes from anchoring on change, not on a
    timer.
Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

07 - Friend: mutual-consent relationships (tm_social-friend)

Status: IMPLEMENTED (overlay.peck.to; consumed live by peck.to —
friendship gates direct messaging). Friend is a RELATION, not an identity
primitive — hence the tm_social-* family — but it uses the same BRC-3
self-attestation envelope as 06-identity-attestations.md.

0. Why friend ≠ follow

Follow is one-way interest. Friend is a mutual relationship that gates
person-to-person capabilities (DMs first). In the BAP era the friend record
existed to publish a messaging public key; under BRC-100/BRC-42 key
exchange is implicit (ECDH between identity roots), so the record's job
reduces to what actually remains: consent + discovery.

1. Record (normative)

MAP SET in OP_FALSE OP_RETURN, BRC-3 self-signed (envelope and
verification exactly as in spec 06 §0):

PREIMAGE: app type schema_version identity peer serial
fields:   type     = "friend" | "unfriend"
          identity = SENDER identity root pubkey (66-hex compressed)
          peer     = TARGET identity root pubkey
          serial   = random nonce (= BRC-42 keyID for sig)
          sig      = BRC-3 self-signature, protocolID [1,'friend'],
                     counterparty = identity
constraints: identity != peer; both match /^0[23][0-9a-fA-F]{64}$/

Only the sender's signature exists on a record: you can declare or revoke
your own side of a relationship, never your peer's.

2. Mutuality (normative)

friend(A→B)               = request
friend(B→A)               = accept
ACTIVE  ⇔  both directions admitted and unrevoked

Two independent one-way attestations form a pair. There is no distinguished
"accept" record type — the second direction is the acceptance. Asynchronous
by construction: each side signs from their own wallet, in their own time.

3. Revocation (normative)

type=unfriend (same preimage shape, same signature scheme) revokes the
emitting side's
direction: the overlay marks every unspent
friend(identity→peer) row spent. The peer's direction is untouched — if A
unfriends, B's standing consent remains, so A sees an incoming request
and can re-accept later with a single new friend(A→B). Withdrawing a
not-yet-accepted request is the same operation.

4. Read API (live)

GET /v1/friends/:subject        # subject = identity root pubkey
-> { light: { mutual:      [{peer, handle}],
              pending_in:  [{peer, handle}],
              pending_out: [{peer, handle}] },
     data:  { outgoing: [...], incoming: [...] } }   # legacy BAP-era rows

handle is hydrated from the canonical light handle claim (spec 06 §3).
Incoming friend records also surface as type=friend_request items in
GET /v1/notifications/:address (resolved through the key-binding layer).

5. Legacy Bitcoin Schema compatibility

The record IS Bitcoin Schema — same MAP envelope, same type=friend
vocabulary — so any bmap-style indexer parses it; it simply carries
identity/peer/sig instead of the BAP era's bapID/publicKey
(which existed to publish a key that BRC-42 now derives). Legacy rows are
indexed and served display-only: they never count toward mutual, and
there is no cross-era identity mapping.

6. What friendship gates (informative)

The reference deployment gates direct messaging on mutual friendship:
DM content is BRC-42-encrypted between the identity roots
(protocolID [2,'peck dm']) and transported via a BRC-103 mutual-auth
message-box — the consent handshake lives on-chain, the message content
never does (an optional publish-permanent path, which would also be
Bitcoin-Schema type=message compatible, is roadmap). Enforcement today is
client-side; receiver-side filtering against the friend graph is the
intended hardening.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

06 - Light identity attestations (key-binding, profile, handle, certs)

Status: IMPLEMENTED (overlay.peck.to; consumed live by peck.to). This is
the light identity family — plain OP_FALSE OP_RETURN MAP records with
BRC-3 self-signatures — that complements the token-state identity of
01-token-state.md. Where ProfileToken is sovereign
stateful identity (spend-to-update), the light family is sovereign
attested identity: cheap, wallet-native, no contract toolchain required.
Resolution prefers light attestations and falls back to token-state.

0. Shared envelope and signature scheme (normative)

Every record is a MAP SET inside OP_FALSE OP_RETURN
(MAP prefix 1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5), with fields as
key value push pairs. All records are self-signed under BRC-3:

preimage  = UTF-8 bytes of JSON.stringify(core, sortedKeys)
            where core = the record's PREIMAGE fields (see each type)
sig       = createSignature({ data: preimage,
                              protocolID: [1, '<type>'],
                              keyID: <serial> })
verify    = ProtoWallet('anyone').verifySignature({
                data: preimage, signature: sig,
                protocolID: [1, '<type>'], keyID: <serial>,
                counterparty: <identity pubkey> })
  • serial is a per-record random nonce (hex). It doubles as the BRC-42
    key derivation keyID, so each record verifies against a distinct
    derived key of the same identity root.
  • schema_version is "1" for everything below.
  • identity is always the identity ROOT pubkey (66-hex compressed,
    wallet.getPublicKey({identityKey: true})), never a derived posting key.
  • Admission = signature verifies + fields well-formed. Nothing else. Topic
    managers are app-agnostic: any wallet can emit these.

1. Key-binding (type=keybinding, topic tm_key-binding)

Binds a derived posting key (what actually signs feed content) to an
identity root, with consent from both keys:

PREIMAGE: app type schema_version subject certifier serial purpose revocation [boundAt]
fields:   subject    = posting pubkey (the bound key)
          certifier  = identity root pubkey
          purpose    = e.g. "social-posting"
          revocation = outpoint reserved for future revocation ("00…00.0" = none)
          sig        = certifier's BRC-3 sig   (protocolID [1,'keybinding'], keyID=serial)
          subjectsig = subject's signature over the SAME preimage
                       (protocolID [1,'identity'], keyID '1', counterparty 'self')

Mutuality is the point: the identity attests "this key posts for me" AND the
key attests "I belong to this identity". One signature alone admits nothing.

Effect (normative): resolution collapses bound keys. Anywhere an author,
actor, or subject appears as a bound posting key or its P2PKH address,
identity resolution returns the root's canonical identity
(POST /v1/identities/resolve, both lanes). Earliest unspent binding per
key wins.

2. Light profile (type=profile, topic tm_identity-profile)

PREIMAGE: app type schema_version identity display_name avatar bio serial

Latest admitted record per identity is canonical. Preferred over legacy
ProfileToken state at resolution (display name / avatar / bio override).

3. Light handle claim (type=handle, topic tm_identity-handle)

PREIMAGE: app type schema_version identity handle serial
handle:   /^[a-z0-9_]{1,30}$/

Canonical owner per handle is FIRST-COME: the earliest admitted, unspent
claim wins a contested handle (Zooko-triangle decision: claimed, not
unique-by-fiat; deterministic and squatting-resistant). The identity's
self-signature proves the claim is genuinely theirs; first-come decides who
wins. GET /resolve/:handle resolves through light claims first, legacy
HandleTokens second.

4. Cert publication (type=cert, topic tm_identity-cert)

The identity self-publishes which BRC-52 certificates it wants public
(identity, certifier, cert_hash, optional platform). All unspent
publications are listed under the identity; cert content verification
happens at resolution, not admission. Bring-your-own-certifier: trust is
federated per certifier key, not centralized.

5. Read API (live)

POST /v1/identities/resolve     # batch; collapses bound keys (pubkey + address lanes)
GET  /v1/identities             # discovery: canonical identities, newest first
GET  /resolve/:handle           # light claim first, legacy HandleToken fallback
GET  /identity/:pubkey/signing-keys

6. Born-on-the-overlay bootstrap (informative)

A new user can be created in ONE transaction with three outputs —
key-binding + light profile + light handle — submitted once with all three
topics. One wallet approval, full identity. This is what the reference
onboarding does.

7. Relationship to token-state identity

Both families coexist by design. Token-state (ProfileToken/HandleToken)
carries script-enforced update rules; the light family carries
zero-toolchain self-attestation. Resolution order: light attestations
preferred, token-state fallback, bound-key collapse applied to both.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

05 - Additional write channels & token economics (ROADMAP)

Status: ROADMAP - designed, not deployed. None of the channels or token-economics flows below are running. They are recorded so implementers know the intended shape.

1. Input-script-data (payment-coupled actions)

Data carried in the unlocking script of a normal P2PKH spend via <chunk> OP_DROP pairs before <sig> <pubkey>. The data rides the spend, so payment + record are one event. Natural fit: tip+comment, paid-follow, paywall receipts, endorsements. Same canonical AIP preimage rules as OP_RETURN (OP_DROPs are not part of the preimage). Requires indexers to scan input scripts, which legacy Bitcom parsers do not.

2. PostToken family & marketplace

Stateful social content as 1Sat tokens (PostToken, and per-app subtypes CanvasToken/EventToken/ChannelToken). subject must own a canonical HandleToken to mint. v1 surface: update, transfer, burn, reply; edits = burn+mint-child. Marketplace (sell, bid/settle, royaltyBps) and like-as-payment are unbundled to a later wave pending real usage data.

3. Paywall via BRC-104 channels

Per-post paywall: AES-encrypt content (UHRP), set priceSats in token state; reader pays owner (one-shot or via BRC-104 channel), overlay verifies and releases the decryption key. No token UTXO spend per read. (BRC-104 is UHRP content-addressing; the paywall enforcement is a payment-channel sCrypt contract + receipts - do not conflate.)

4. Teranode Kafka direct-consume

Migrate the indexer read-side off third-party relays onto Teranode Kafka topics (validatortxs, txmeta, subtrees, blocksFinal, invalidation topics). Lower latency, no third-party dependency, native protobuf. Not a blocker; the current relay path works for OP_RETURN.

5. Bitcoin-Schema hybrid bridge for token-state posts

"Strawman A": a token-state-update TX MAY include an extra OP_RETURN Bitcoin Schema MAP post (opt-in per TX via a post_content field) so strict-v1 consumers see it as an ordinary post, while token-aware consumers parse both layers and link via outpoint. Canonical output order: [0] state UTXO, [1] token-payload OP_RETURN, [2] BS-MAP (only if post_content set), [3] change. Indexer binds token_ref only when output[0] matches a known token locking script (anti-spoof). Empty/undefined post_content means silent update (no feed entry).

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

04 - Federation (ROADMAP)

Status: ROADMAP - not implemented. Today exactly one overlay (overlay.peck.to) serves state. Nothing in this file is running. It documents the intended direction so the single-overlay design leaves room to grow without protocol breakage.

1. Why federation

As BSV scales (Teranode era), single-overlay re-derivation from chain becomes uneconomic, and a single overlay is a trust bottleneck. The intended model is a federation of specialized overlays that sync topic-state peer-to-peer and keep each other honest via game theory.

2. Planned mechanics

  • Topic-rooted Merkle trees served at /v1/topic/{topic}/root, /snapshot?block=N, /delta.
  • GASP / subtree-based sync between overlays, mirroring Teranode's subtree architecture; near-constant-time bootstrap regardless of chain depth.
  • On-chain state-root anchoring — graduated to IMPLEMENTED: prev-chained 1Sat anchor ordinals, anchor-on-change scheduler, GET /state exposes anchor.matchesLive. See 08-state-anchoring.md.
  • N-of-M peer acceptance - apps may require agreement among M peers before treating a record as authoritative; an overlay publishing an inconsistent root becomes detectable and isolatable.
  • Idempotent submission - every submission carries a content-hash/nonce; repeat submissions return the same response (>=24h dedup window), eliminating the missed-response failure mode.

3. Formal grounding (reference, not a dependency)

The federation's convergence/equilibrium claims are intended to be reasoned about under an automata-theoretic / game-theoretic frame (bounded conformance, resilient Nash equilibria, Merkle-proof state representations, partition reconciliation). This is a theoretical anchor for the design - it is not running code and must not be cited as an implemented guarantee.

4. Open questions

Cross-overlay subtree-format standardization; anchor cadence (per-block vs per-N); peer discovery (out-of-band registry -> token-state registry contract); federation membership (open vs permissioned); disagreement resolution (Merkle-walk to divergent leaves).

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

03 - Overlay topics & REST hydration

Status: IMPLEMENTED (single overlay) - overlay.peck.to. Federation/sync between overlays is ROADMAP (04-federation-roadmap.md).

Applications read from the overlay, not from chain directly. The overlay runs BRC-22 topic managers that admit outputs and lookup services that hydrate REST queries.

1. Topic managers (live)

Topic Admits Spec
tm_social-content Bitcoin Schema OP_RETURN content (posts, likes, follows, ...) 00
tm_social-profile ProfileToken (token-state) 01
tm_social-handle HandleToken (token-state) 01
tm_social-friend friend/unfriend attestations (mutual consent) 07
tm_key-binding posting-key ↔ identity-root bindings 06
tm_identity-profile light profile attestations 06
tm_identity-handle light handle claims (first-come) 06
tm_identity-cert BRC-52 cert publications 06
tm_overlay-anchor state-root anchor ordinals 08

A lookup service: parses the layers, upserts a row keyed by outpoint, recomputes canonicality (canonical rule in 01-token-state.md), persists Atomic-BEEF alongside the row for self-verifying hydration.

2. Topic-rooted state (normative)

Each topic maintains a state representation committed as a Merkle root, so topic-state-at-block-N is a verifiable commitment, not just a cache. The roots are anchored on-chain and auditable — see 08-state-anchoring.md. Inter-overlay sync remains roadmap (04).

3. REST hydration (live)

GET /v1/feed?app=&limit=&offset=
GET /v1/post/:txid  /v1/thread/:txid  /v1/search?q=
POST /v1/identities/resolve               -> see 02
GET /identity/:pubkey  /resolve/:handle   -> see 02
GET /v1/bio/profile  /v1/bio/handle/:handle  /cert/verify/:sha

Pagination uses a structured cursor (before_height + before_outpoint tiebreaker), never offset, for token queries.

4. Submission

Clients submit transactions to the overlay asynchronously (peck-broadcaster pattern). Idempotent submission (dedup by content-hash/nonce) is partially present and formalized in the federation roadmap.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

02 - Identity projection (the killer feature)

Status: IMPLEMENTED (overlay.peck.to, consumed live by peck.to). Reference: peck-web/overlay_client.py::resolve_identities + peck-web/routers/pages.py::_enrich_with_profile_tokens.

Identity projection turns raw on-chain authors (P2PKH addresses or pubkeys) into rich, canonical identities at render time - one batch call per feed page, with the renderer defensively overwriting display name / avatar / handle. It gives cross-app SSO-feel with no server session and no central identity registry: identity lives in token-state, the overlay just projects it.

1. Batch resolve (normative)

POST /v1/identities/resolve
body: { "addresses": ["<base58>", ...], "pubkeys": ["<hex>", ...] }   // either or both, max 200 each
-> 200 { "identities": { "<input-key>": {
        "pubkey": "<hex>",
        "address": "<base58>",
        "handle": "<handle|null>",
        "displayName": "<string|null>",
        "avatarRef": "<uhrp://...|null>",
        "profileOutpoint": "<txid.vout>"
      } } }

Rules:

  • The result is keyed on the exact input string: an address-input yields an address-key, a pubkey-input yields a pubkey-key. Feed authors are P2PKH base58 (the author column) so pass them as addresses. Pubkey-hex subjects go in pubkeys.
  • Inputs without a canonical, unspent ProfileToken are omitted from the dict (ghost authors). This is intended, not an error.
  • Call it once per feed page, never per row.

2. Companion lookups (normative)

  • GET /identity/:pubkey - full bundle for one identity.
  • GET /resolve/:handle - handle to pubkey + deep-links into consuming apps.
  • GET /v1/bio/profile?subject=<pk> | owner=<pk> | outpoint=<txid.vout> - raw ProfileToken state.
  • GET /v1/bio/handle/:handle - HandleToken by handle.
  • GET /cert/verify/:sha - verify a BRC-52 cert reference.

3. Defensive overwrite (normative)

The renderer treats overlay ProfileToken state as canonical and overwrites the post's own display name / avatar / handle - but the enrichment must never break the feed. The reference does exactly this: one batch call, overwrite only the keys present, return the input untouched on any error or empty result.

# reference shape - overlay_client.resolve_identities + pages._enrich_with_profile_tokens
async def enrich(posts):
    addrs = [p["author"] for p in posts if p.get("author")]
    idents = await resolve_identities(addresses=addrs)   # {} on any error -> feed never bricks
    for p in posts:
        ident = idents.get(p["author"])
        if not ident:                # ghost author: no ProfileToken yet -> leave fallback
            continue
        if ident.get("displayName"): p["ui_display_name"] = ident["displayName"]
        if ident.get("handle"):      p["ui_handle"]       = ident["handle"]
        if ident.get("avatarRef"):   p["u_avatar_url"]    = uhrp(ident["avatarRef"])
        p["profile_outpoint"] = ident.get("profileOutpoint")
    return posts

4. Why this is the killer feature

  • No server session. The identity is projected from chain state on every render; any app reading the same overlay sees the same identity. That is SSO without a session store.
  • Sovereign. The user owns the ProfileToken; the overlay cannot forge displayName/avatar - it only projects what the token says.
  • Composable. /resolve/:handle returns deep-links so peck.bio, peck.to, peck.ink resolve the same handle to their own surfaces.

5. Data reality (read this)

As of this writing only ~4 ProfileToken owners exist on mainnet. For every other author resolve returns nothing, and the renderer falls back to a truncated address (or a legacy users-table row, where one exists). So today the projection lights up for a handful of identities and is a graceful no-op for the rest. It is correct and forward-compatible: as users mint ProfileTokens (or existing v0 profiles are backfilled), they light up automatically with zero renderer changes. Do not design UX that assumes a populated identity graph yet.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

01 - Token-state: ownership-bearing entities

Status: IMPLEMENTED for ProfileToken & HandleToken (live on mainnet, decoded by overlay.peck.to). PostToken family and marketplace methods are ROADMAP - see 05-write-channels-roadmap.md.

Identity entities live as 1Sat UTXOs whose locking script encodes both data and cryptographically-enforced update rules. The overlay decodes the current state and tracks the canonical, unspent token per subject.

1. Three-layer envelope

output value: 1 satoshi
output.lockingScript:
  Layer A - ord-inscription:  OP_FALSE OP_IF "ord" OP_1 "application/json" OP_0 <state-json> OP_ENDIF
  Layer B - MAP tags:         OP_FALSE OP_RETURN MAP SET app/type/subject/... [| AIP]
  Layer C - sCrypt state:     prop encodings + state pushes + OP_PUSH_TX continuation enforcement
  • Layer A gives 1Sat-ordinals indexer compatibility.
  • Layer B gives Bitcoin Schema indexer routing (no sCrypt eval needed to discover the token).
  • Layer C is canonical truth: mutable economic/state fields, monotonic version, transfer/update enforcement via OP_PUSH_TX hashOutputs.

Disagreement between layers means a malformed token (rejected at admission).

2. State decode (normative)

The overlay extracts state from the data pushes that precede the contract template. For the live identity tokens:

  • HandleToken - {handle, owner, version}; handle matches /^[a-z0-9_]{1,30}$/, case-insensitive. One canonical handle per owner.
  • ProfileToken - {subject, owner, displayName, avatarRef (uhrp://...), bioPageRef, cert_refs[], version}. subject is the immutable author identityKey; owner is the current (derived, transferable) spend authority. subject != owner is the load-bearing separation.

3. Owner-authority derivation (normative)

owner is a derived pubkey, never the root identity key:

protocolID:   [2, '<family> owner-authority']   // security level 2 = prompt per use
keyID:        sha256(<outpoint>).slice(0,16)     // per-token scoping
counterparty: 'self'

4. Canonicality

When multiple tokens claim the same subject/handle, the canonical one is the latest in the parent-chain resolved by lowest block-height + lex-min outpoint, NULL-height sorts last. Edits are burn-parent + mint-child (parent_outpoint names the original); contentRef is immutable.

5. Data reality

Only a handful of ProfileToken owners exist on mainnet today. The decode path is live and correct, but the population is small - see the data-reality note in 02-identity-projection.md.

Read more →
@overlaysocial 1H59Hb…vBTV Key · overlay.social

00 - Content: Bitcoin Schema admission

Status: IMPLEMENTED (overlay.peck.to, peck-indexer-go AIP verifier, topic tm_peck).

The baseline write channel. Content is carried in an OP_FALSE OP_RETURN output using the Bitcoin Schema B + MAP + AIP push stack. Every Bitcom-aware indexer - and overlay.peck.to - can read it.

1. Output shape

OP_FALSE OP_RETURN
  <B_PREFIX>  <content>  <media-type>  <encoding>
  | (0x01 0x7c)
  <MAP_PREFIX>  SET  <key1> <val1>  <key2> <val2> ...
  | (0x01 0x7c)
  <AIP_PREFIX>  BITCOIN_ECDSA  <signing-address>  <signature>

2. MAP key namespace

Keys are emitted both bare and BRC-43-namespaced (2/peck.to-social/v1.<key>). Core keys:

Key Meaning
app App discriminator (peck.to, peck.ink, ...)
type post, reply, repost, like, follow, tag, message
content Inline UTF-8 content (or via B payload)
reply_tx Parent txid (threading)
context URN cross-ref (tx:, url:, image:)
cert_ref BRC-52 cert txid

3. Canonical AIP preimage (normative)

The AIP signature covers every preceding pushdata-content byte (B + MAP + AIP-header up to and including signing-address), with pipes counted as 0x7c data bytes. Opcodes (OP_FALSE, OP_RETURN, OP_DROP) are NOT part of the preimage - the verifier walks the data pushes only. This rule is channel-uniform: it is identical for OP_RETURN and for the roadmap input-script channel.

Today the signature algorithm is BSM-compact (BITCOIN_ECDSA). A BRC-77 algorithm marker is reserved for v2.

4. Pushdata gotchas (normative)

  • Push every chunk via the SDK's Script.writeBin wrapper. Do not hand-prepend length bytes - @bsv/sdk silently mis-encodes raw pushdata.
  • The pipe separator MUST be pushed as 01 7c <pushdata>, never a raw 0x7c byte (that decodes as OP_SWAP).

5. When to use this channel

High-volume, attribution-only, discoverable-by-everyone content: posts, likes, follows, tags, channel messages. For ownership-bearing entities, use token-state (01-token-state.md).

Read more →