Skip to main content

The dig:// remote (clone/pull/push)

Normative spec

The authoritative §21 transport wire spec — the REST surface, the JSON-outer/Chia-codec-inner envelope, 2-leg push v1, the authenticated head, and per-request §21.9 auth — is Protocol · §21 transport & push. This page is the CLI task guide.

On top of the read interface, dig-store has a git-style remote. You clone a store to disk, pull new generations, and push a new generation — over the same routes a node already serves. The transport is named by a dig:// URL, and every request is signed by your identity key.

digs clone dig://5b1f…e9 # resolves your node (see below), else rpc.dig.net
digs pull # sync new generations
digs push # publish a new generation
dig:// is the CLI/remote scheme; chia:// is the browser address bar

dig:// names a remote origin for the dig-store CLI (clone/pull/push). The DIG Browser's typeable content address is chia:// — the same store identity in a form you paste into the address bar. The underlying urn:dig: URN namespace is unchanged.

The dig:// scheme

A dig:// URL names the store's owner (an informational namespace, like GitHub's user/) and, optionally, the node host that serves it — a store can have many origins — and resolves to https://<host>/stores/<storeId> under the hood.

You write…It resolves to…Notes
dig://<storeId>https://<resolved-node>/stores/<storeId>Bare 64-hex store id; no host given, so the CLI resolves a node — see Which node it talks to below.
dig://<user>@<storeId>https://<resolved-node>/stores/<storeId><user> is the owner handle — display only; host still resolved.
dig://[<user>@]<host>[:port]/<storeId>https://<host>[:port]/stores/<storeId>An explicit host in the URL always wins: the reference node, a third party, or one you run.

The owner segment never changes which bytes you fetch — content is addressed by the chain-anchored storeId and verified against the on-chain root, so any origin returns the same store. DIGHUb shows a store's canonical origin in a GitHub-style clone box as dig://<handle>@rpc.dig.net/<storeId>.

Which node it talks to

When a dig:// URL doesn't name a host, the CLI resolves one by trying, in order, the first that responds:

  1. An explicitly-configured node — the --node <url> flag, the $DIG_NODE_URL environment variable, this project's digs config node.url --local <url> value, or the machine-wide digs config node.url <url> value. Sourced in that order, and this always wins over the automatic steps below.
  2. dig.local — your installed local dig-node (https://dig.local, then http://dig.local).
  3. localhost — a dig-node on the loopback address, http://localhost:9778 (or $DIG_NODE_PORT).
  4. rpc.dig.net — the public gateway, used only when no local node answers.

An unconfigured origin follows this same order, so it means your node by default. Publishing (push, revoke) requires one of tiers 1–3: rather than send your content and your request signatures to a public server you never chose, it stops and tells you how to start or install a node.

Each tier is a cheap health probe with a short timeout, so an unreachable local node falls through quickly rather than hanging a clone/pull/push. See Which node dig-store talks to for how to set an override, and Point a consumer at your node for the same ladder as it applies to the DIG Browser and extension.

Today every tier is reached over plain HTTPS (loopback tiers over plain HTTP), and the per-request signing described below is what authenticates you — not the transport. Mutual TLS with a client certificate derived from your identity key is specified for node-class clients but is not yet wired; when it lands, the same signed requests will ride on top of that authenticated channel rather than being replaced by it.

Every request is signed (per-request auth)

Even an anonymous clone is signed. Each request carries a signed message from your identity key — a user-global BLS key, distinct from a store's signing key, stable per user/machine like an SSH key. (A store's signing key authorizes writes to that store; the identity key authenticates the caller.) Four headers carry it:

HeaderValue
X-Dig-Identity48-byte-hex BLS G1 identity public key
X-Dig-Timestampunix seconds
X-Dig-Nonce32-byte hex, fresh per request
X-Dig-Auth96-byte-hex BLS signature over the canonical message

The canonical message binds the route's logical op, the store, and the freshness fields:

msg = SHA-256( REQ_DST || len(method) || method || store_id(32) || timestamp_be(8) || nonce(32) )

REQ_DST = "dig-store:req:v1"
method = one of { fetch, roots, module, content, proof, push, tombstone, delta }
len(...) = big-endian u32 length of the method string

Binding method stops a signature for one route being replayed against another. The server returns 401 on a missing/malformed header, a timestamp outside the ±300 s freshness window, or a signature that does not verify. The freshness window plus the per-request nonce defeat replay.

The identity key is created once and reused for every request from that machine. It lives at <OS config dir>/dig/identity_key.bin (override with DIG_IDENTITY_DIR).

The remote routes

These are the routes a node serves under /stores/{id} — the same surface rpc.dig.net exposes. Every request is authenticated as above.

RouteReturns / Body
GET /stores/{id}StoreDescriptor{ current_root, size, public_key, push_sig, tombstones[] }
GET /stores/{id}/rootsRootHistory{ roots: [ { generation, root, timestamp } ] }, oldest→newest
GET|HEAD /stores/{id}/module?root=<hex>Raw .dig module bytes; ETag = root, If-None-Match304. This is the clone/pull download. Serves inline up to ~5 MiB — a larger capsule returns 413 naming dig.getCapsule (see Large capsules). HEAD advertises the full Content-Length.
PUT /stores/{id}/modulePush a new generation (self-hosted nodes). Body = module; headers below.
POST /stores/{id}/tombstoneA signed revocation.

A PUT push also carries the store-write headers: X-Dig-Parent (the root it extends), X-Dig-Root (the new root), X-Dig-Signature (a BLS signature by the store signing key over SHA-256(root || store_id)), and X-Dig-Push-Mode (advance to move the head, or pending to stage).

clone and pull

A clone is a sequence of authenticated reads of public ciphertext and metadata: the descriptor (GET /stores/{id}), the generation history (GET /stores/{id}/roots), and the module bytes for each wanted generation (GET /stores/{id}/module?root=<hex>). A pull re-reads descriptor + roots and downloads only the modules you lack; an unchanged head answers 304. Each module is verified against its on-chain root client-side, so the node is never trusted to have returned genuine bytes.

Large capsules stream in chunks

The inline GET /stores/{id}/module download serves a whole capsule in one response, capped at ~5 MiB — the public gateway serves it through a serverless tier with a ~6 MB response ceiling, so a bigger capsule can't come back in a single body. A capsule over the cap therefore returns 413 with a body that names the streaming method to use instead:

{ "error": "capsule_too_large_for_get", "total_length": 141557760,
"rpc_method": "dig.getCapsule", "store_id": "5b1f…e9", "root": "9c2a…f0" }

Download a whole/large capsule over the dig.getCapsule chunk loop: request (store_id, root, offset, length), follow next_offset until complete, and reassemble — see Streaming. A HEAD /stores/{id}/module returns the full Content-Length, so a client learns the size and picks its transport before pulling any bytes. dig-store clone/pull and the dig-node §21 client already do this automatically — they stream large capsules over dig.getCapsule and only use the inline GET for small ones.

push: DIGHUb vs. self-hosted

Publishing a generation is an on-chain event with a fee, so where it lands depends on the origin:

  • Push to DIGHUb (rpc.dig.net) does not use the PUT route. DIGHUb anchors every generation on-chain and each generation pays an on-chain $DIG fee; DIGHUb holds no keys, so the push goes through its authenticated, wallet-signed /v1 flow, where you sign the on-chain spend. DIGHUb accepts only stores already registered on-chain with the capsule price in $DIG paid — that registration is the launch gate.
  • Push to a self-hosted node uses PUT /stores/{id}/module directly: the node accepts the module on a valid store-key signature and advances (or stages) its head.

In both cases the store-key signature over SHA-256(root || store_id) authorizes the write; only the on-chain settlement differs.

Run your own node

Because reads are blind and client-verified, anyone can host an origin:

digs serve --bind 0.0.0.0:8443

This serves the full remote protocol — descriptor, roots, module download, and PUT push — for a store straight from disk. Others clone it by pointing a dig:// URL at your host:

digs clone dig://yourhost:8443/<storeId>

A self-hosted node speaks the identical routes and the identical per-request auth as the reference node, so a store is portable across origins with no client change.

CLI summary

CommandDoes
digs remote add origin dig://…Register a remote origin for the local store.
digs clone dig://…Fetch descriptor, roots, and module(s) into a new local store.
digs pullSync new generations from the origin.
digs pushPublish a new generation (DIGHUb /v1 or self-hosted PUT).
digs serve --bind 0.0.0.0:8443Serve the remote protocol for a local store.
note

The chia:// remote and the public read RPC speak the same routes against the same nodes. The full normative specification is Part 5 (§22) of the dig RPC: Network Content Interface document.