Introduction

Concepts & data model

Four layers: sector, shard, slab, object. Every endpoint is shaped by them.

Sector

The unit a provider stores: 4 MiB. Every sector is addressed by its Merkle root, a 32-byte hash. That root is how you ask for it back and how integrity is checked. A provider that returns different bytes returns bytes that do not hash to the root, and your client rejects them.

Shard

One erasure-coded fragment, stored as one or more sectors on one provider. Default geometry is 30 shards: 10 data, 20 parity. Shard size is ceil(sliceSize / minShards).

Slab

One erasure-coded stripe: the shards that decode together.

FieldMeaning
minShardsShards needed to reconstruct. 10 by default.
sectors[]One entry per shard: provider public key, sector root, contract.
encryptionKey32 bytes of encryption parameter for this slab.
versionSlab format version.

A slab's ID is a digest of its parameters, not a random id and not a hash of the provider set. Repair can move a shard to another provider without the slab ID changing, so object references stay valid.

Slab geometry is validated

Redundancy (totalShards / minShards) must be at least 1.5× and at most 4.0×. No provider may appear twice in one slab. Duplicate sector roots are rejected. encryptionKey must be exactly 32 bytes. Violations are rejected at registration.

Object

An ordered list of slab segments, plus wrapped keys and signatures.

FieldMeaning
id32-byte object key. You choose it. Usually a hash of the content.
slabs[]{ id, offset, length } — a byte range in a slab. Concatenated in order, these are the object.
encryptedDataKeyThe object's data key, wrapped. Tessera cannot unwrap it.
encryptedMetadataAn opaque blob. Tessera never interprets it.
encryptedMetadataKeyThe wrapped key for that blob.
dataSignature, metadataSignatureYour signatures over the respective parts.

Several small objects can share a slab. One large object can span many. Deduplication and packing fall out of the model.

What the object model does not contain

No filename, no path, no directory, no content type, no cleartext size. The columns do not exist.

If you need a file browser, you build the naming layer:

const metadata = {
  name: 'quarterly-report.pdf',
  contentType: 'application/pdf',
  size: 8_431_233,
  path: '/reports/2026/q1/',
  createdAt: '2026-04-02T09:14:00Z',
}
// encrypt(metadata) → encryptedMetadata

To list a directory you decrypt the metadata of the objects you know about, which means you keep a local index. You take on an index. In exchange no one — including Tessera, including anyone who compels Tessera — can enumerate what you stored or infer it from names.

Design your keys deliberately

The object key is the only server-side handle, and you choose it. A content hash gives free deduplication. A deterministic hash of your own path gives lookup by path without an index. A random key gives unlinkability. Pick before you have a million objects.

Account

The account on the storage API is the credential: the ed25519 public key you were issued. No username, no email, no separate account object in the storage path.

Every slab and object is bound to the credential that created it. A request signed by a different credential is 403. A new credential is a new namespace. There is no credential that belongs to an account that owns objects.

The credential is the account

Losing the private key means losing access to everything stored under it. Tessera cannot restore it. We never had it. Back it up before you store anything you care about.

How the layers stack

Object  "report.pdf"        ← your metadata says so; we do not know
  ├── slab A, offset 0,       length 4194304
  └── slab B, offset 0,       length 4236929
                 │
                 └── Slab B: minShards 10, 30 sectors
                       ├── shard  1 → provider ed25519:a1b2… root 9f3c…
                       ├── shard  2 → provider ed25519:c3d4… root 22ab…
                       ├── …
                       └── shard 30 → provider ed25519:e5f6… root 7c1d…

Repair

Slab health is scanned continuously. When a provider goes away, the shard is reconstructed from parity, written to a replacement provider, and the slab record is updated in place. The slab ID does not change.

You do not trigger repair, pay for it as an event, or read the data for it to happen.

Encoding conventions

TypeWire format
Slab ID, object key, sector rootLowercase hex, 64 characters
Provider public keyed25519: followed by 64 hex characters
credential query parameterbase64url with padding
appKey from provisioningbase64url without padding
Byte-slice JSON fields (encryptionKey, encryptedMetadata, …)Standard base64 with padding
TimestampsRFC 3339