Skip to main content
Core is an advanced internal implementation. You do not need it for typical usage such as posting, fetching feeds, or notifications. Refer to this page when working directly with AT Protocol data structures.

AT Protocol data model overview

AT Protocol repositories use a content-addressed data structure. The following elements are involved in storage, transfer, and verification:
ClassRole
CBORBinary serialization
CIDContent address (hash identifier)
CARRepository archive read/write
TIDTime-ordered record keys
VarintVariable-length integer encoding (internal)

CBOR

The CBOR class provides encoding and decoding for the DAG-CBOR format used by AT Protocol. It extends standard CBOR with support for CID links (tag 42).

Encode

use Revolution\Bluesky\Core\CBOR;

$record = [
    '$type'     => 'app.bsky.feed.post',
    'text'      => 'Hello, Bluesky!',
    'createdAt' => '2025-01-01T00:00:00.000Z',
];

$bytes = CBOR::encode($record); // binary string

Decode

use Revolution\Bluesky\Core\CBOR;

// Decode a single item
$data = CBOR::decode($bytes);

// Decode the first item from a stream and return the remainder
[$item, $remainder] = CBOR::decodeFirst($bytes);

// Decode all items from a stream
$items = CBOR::decodeAll($bytes);

Use case

When you need to compute or verify the CID of a record manually, CBOR encoding is required (see also the verify page).
use Revolution\Bluesky\Core\CBOR;
use Revolution\Bluesky\Core\CID;

$record = data_get($block, 'value');
$cbor   = CBOR::encode($record);
$bool   = CID::verify($cbor, data_get($block, 'cid'));

CID (Content Identifier)

A CID is a self-describing hash-based identifier for content. AT Protocol uses a SHA-256 hash wrapped in multihash, then encoded with multicodec and multibase.

CIDv0 vs CIDv1

VersionEncodingPrefixUse
v0base58btcQm...Older spec (blobs)
v1base32bafy...Current spec (records)

Main API

use Revolution\Bluesky\Core\CID;

// Generate a CID from data
$cid = CID::encode($data, CID::DAG_CBOR); // for records
$cid = CID::encode($data, CID::RAW);      // for binary (images, etc.)

// Verify data against a CID
$bool = CID::verify($data, $cid, CID::DAG_CBOR);
$bool = CID::verify($file, $cid, CID::RAW);

// Parse a CID
$decoded = CID::decode($cid); // ['version', 'codec', 'hash']

// Detect CID version
$version = CID::version($cid); // 0 or 1

// Convert between CID string and raw bytes
$bytes = CID::decodeBytes($cid);
$cid   = CID::encodeBytes($bytes);

CAR (Content Addressable aRchive)

A CAR file stores an AT Protocol repository as a sequence of CBOR-encoded blocks. Data retrieved by com.atproto.sync.getRepo is in this format.

Decode

use Revolution\Bluesky\Core\CAR;

// Decode the entire CAR file (roots + all blocks)
['roots' => $roots, 'blocks' => $blocks] = CAR::decode($carData);

// Get only the root CIDs
$roots = CAR::decodeRoots($carData);

// Iterate over blocks
foreach (CAR::blockIterator($carData) as [$cid, $block]) {
    // $cid: CID string, $block: binary data
}

// Get records as a map
foreach (CAR::blockMap($carData) as $key => $record) {
    // $key: record key, $record: decoded array
}

Verify the signed commit

To confirm that a CAR file belongs to a specific user, verify the Signed Commit signature against the public key in the DID Document.
use Revolution\Bluesky\Core\CAR;
use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Support\DidDocument;

$did = 'did:plc:***';

$didDoc    = DidDocument::make(Bluesky::identity()->resolveDID($did)->json());
$publicKey = DidKey::parse($didDoc->publicKey());

$signed = CAR::signedCommit($carData);
$bool   = CAR::verifySignedCommit($signed, $publicKey);
See also: DownloadRepoCommand

TID (Timestamp Identifier)

TID is a time-ordered unique ID used as record keys in AT Protocol. It encodes a microsecond timestamp plus a clock ID into a 13-character base32 string.
Example: 3jujm55ngfc24

Generate and convert

use Revolution\Bluesky\Core\TID;

// Generate the next TID
$tid = TID::next();
echo $tid->toString(); // e.g. "3jujm55ngfc24"

// Create a TID from a string
$tid = TID::fromStr('3jujm55ngfc24');

// Create a TID from a timestamp (microseconds) and clock ID
$tid = TID::fromTime(microtime(true) * 1000000, 0);

TID structure

TID::s32encode() and TID::s32decode() convert between the integer representation and the string form.

Varint (variable-length integer)

Varint is an internal utility used for parsing CAR and CBOR binary formats. You normally do not need to use it directly.
use Revolution\Bluesky\Core\Varint;

// Encode an integer
$bytes = Varint::encode(1234);

// Decode from a stream (returns value and bytes consumed)
[$value, $bytesRead] = Varint::decodeStream($stream);

Testing with Core classes

Core classes (CBOR / CID / CAR / TID) do not make any external network calls, so no mocking is needed in tests.
// Can be used directly in tests
$bytes = CBOR::encode(['text' => 'Hello']);
$cid   = CID::encode($bytes, CID::DAG_CBOR);
$bool  = CID::verify($bytes, $cid);

References

Source: src/Core/
Last modified on April 25, 2026