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.
| Field | Meaning |
|---|---|
minShards | Shards needed to reconstruct. 10 by default. |
sectors[] | One entry per shard: provider public key, sector root, contract. |
encryptionKey | 32 bytes of encryption parameter for this slab. |
version | Slab 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.
| Field | Meaning |
|---|---|
id | 32-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. |
encryptedDataKey | The object's data key, wrapped. Tessera cannot unwrap it. |
encryptedMetadata | An opaque blob. Tessera never interprets it. |
encryptedMetadataKey | The wrapped key for that blob. |
dataSignature, metadataSignature | Your 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
| Type | Wire format |
|---|---|
| Slab ID, object key, sector root | Lowercase hex, 64 characters |
| Provider public key | ed25519: followed by 64 hex characters |
credential query parameter | base64url with padding |
appKey from provisioning | base64url without padding |
Byte-slice JSON fields (encryptionKey, encryptedMetadata, …) | Standard base64 with padding |
| Timestamps | RFC 3339 |