Drops Protocol documentation

Application layer · marker drops

Artifacts: small, exact, permanent

A Drops artifact is a media object small enough to sit inside its own proof. There is no separate content server to trust, no envelope to reassemble, and no chunking convention to get wrong. What the leaf holds is what the artifact is.

The 256-byte decision

Most artifact formats on Bitcoin optimise for size: how much can be pushed on chain, in how few transactions. Drops optimises for the opposite property. A body that fits in 256 bytes is a body that can be shown in full next to its own hash, reproduced from a single witness, and reasoned about without streaming.

Self-contained proof

The hash, the bytes and the commitment are all in the same witness. Verifying the artifact requires the reveal transaction and nothing else.

No reassembly

No chunk ordering, no continuation markers, no partial-artifact state. An artifact is present or absent.

Explicit references

Larger material is referenced by a content-addressed pointer written into the body. The reference is visible, so its trust assumptions are visible too.

What actually fits

Realistic bodies within 256 bytes
Content typeWhat fits
text/plainRoughly 256 ASCII characters, or fewer for multi-byte UTF-8. A statement, a name, a key fingerprint, a short poem.
image/svg+xmlA compact vector mark. Geometry, not illustration. Note the serving policy below.
application/jsonA small structured record. The Pacts reference profile is one such body, and lands around 205 bytes at its longest.
application/vnd.drops.pact-seedThe fixed 184-byte Pact Seed record.
A content-addressed pointerA URI naming a hash, for example an IPFS CID or a bare SHA-256 with a retrieval hint. The pointer is on chain; the payload is not.
A pointer is a different promise

When a body holds a reference rather than the content, Bitcoin proves the reference, not the thing referenced. That is still useful: it fixes which bytes were meant. It is not the same as the content being on chain, and an interface should not present the two as equivalent.

Content types

The protocol does not maintain an allowlist. Any string matching the restricted-name grammar is a valid content type in a leaf. That is a deliberate separation: what may be recorded is broad, and what may be rendered is narrow.

  • Lowercase only. Text/Plain is invalid.
  • No parameters. text/plain; charset=utf-8 is invalid; the charset belongs inside the body's own convention.
  • At most 80 bytes, ASCII only.
  • Grammar: ^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$

Serving policy

A conforming implementation that serves artifact bodies over HTTP must assume the body is hostile, because anyone can put anything in one. The reference body endpoint applies a passive-content policy: a small set of types keep their declared media type and render inline, and everything else is served as an opaque download.

Passive inline types, and everything else
Declared content typeServed asWhy
text/plainInline, with its declared typePassive. Cannot execute, cannot fetch, cannot navigate.
image/png, image/jpeg, image/gif, image/webp, image/avifInline, with its declared type
image/svg+xmlapplication/octet-stream attachmentSVG is an active document format. It can carry script and external references.
text/html, application/javascript, application/xml, application/json, application/pdfapplication/octet-stream attachmentActive, script-bearing, or capable of external fetches.
Anything else, including unknown typesapplication/octet-stream attachmentDefault deny. An unrecognised type is not a safe type.

The same response carries the headers that make caching safe and rendering contained:

ETag:                     "<sha256 of the body, quoted>"
Cache-Control:            public, max-age=31536000, immutable
Content-Security-Policy:  default-src 'none'; sandbox
X-Content-Type-Options:   nosniff
Content-Disposition:      inline | attachment, per the table above

A year-long immutable cache is correct here for the same reason the artifact exists: the bytes at a given Drop identity can never change. The ETag is the body hash, so a cache validator and a protocol proof are the same value.

Artifact lifecycle

An artifact has one creation event and any number of custody moves. It has no update, no revision, and no delete.

  1. Committed

    A Taproot output commits to a script tree containing the Drops leaf. Nothing is public yet. An observer sees an ordinary P2TR output.

  2. Revealed

    The output is spent on the script path. The leaf and the control block enter the witness. The artifact now exists on chain but is not yet recorded.

  3. Recorded

    At the implementation's confirmation depth, and only after the commitment verifies, the indexer records the artifact. Its identity is fixed at this point and never changes.

  4. Held

    Custody sits at output 0 of the reveal transaction. Custody status is active.

  5. Transferred

    Spending the custody outpoint moves custody to output 0 of the spending transaction, provided that output is a P2WPKH or P2TR output with a positive value. The transfer count increments.

  6. Ended

    If a spend does not produce a supported output 0, custody status becomes burned. The record remains readable forever; only its custody chain stops.

The full custody rules, including the exact script patterns and the legacy_unresolved status, are normative in specification section 6.2.

A dark studio scene: a bronze Bitcoin disc on a metal plinth with a glowing translucent droplet in front of it, linked by light lines to small illuminated cubes and a checkmark tile.
One creation event, then a custody chain. No revisions, because a revisable record is not the thing this protocol is for.

What an implementation records

These are the fields the reference indexer stores and returns for an artifact. Field names match the API reference.

Artifact record fields
FieldTypeNotes
dropIdstringThe portable identity. The only field safe to key on.
markerdrops or drops-pactFrom the leaf.
mimestringThe declared content type, verbatim.
bodyBytesinteger, 1 to 256Body length.
bodySha25664 lowercase hexEquals the leaf's hash field, and equals SHA-256 of the served body.
dropmark10 uppercase hexDecorative short label. Not an identity.
creatorPubkey64 lowercase hexThe x-only key from the leaf.
initialOwnerAddressstring or nullAddress of output 0 of the reveal transaction.
currentOwnerAddressstring or nullAddress holding the current custody outpoint.
custodyStatusactive, burned, legacy_unresolvedSee specification 6.2.
currentCustodyOutpointobject or nulltxid, vout, scriptHex, valueSats.
custodyobjectProfile drops-custody-v1, plus transferCount, lastTransferHeight, lastTransferTxid, and the projection's verification height and block hash.
revealobjecttxid, input, height, blockHash.
sequence, displayNameinteger, stringPresentation only. Deployment-local.
pactSeed, pactsReferenceobjectPresent when the body matched one of the two Pact body profiles.

Everything above except sequence, displayName and the custody verification metadata is derivable from chain data alone. That is the test for whether a field belongs in a protocol record: could a second implementation compute it and get the same answer?