For a technical reader

How PATINA works

Two marker opcodes, one carrier output per artifact, and a subtraction between block heights. Everything on this page is readable from an ordinary Bitcoin node with no extra index and no extra trust.

This is the mechanism at the level a developer needs before deciding whether to read the specification. It states the same rules the reference implementation follows, in the same order, with the same numbers.

01 The core

Depth is a subtraction, not a counter

An artifact points at exactly one unspent output, its carrier. For a live artifact at chain height H:

depth
depth = H - carrier.height
tier  = the highest ladder index whose threshold is not greater than depth

Depth is computed at query time and never stored per block, so there is no counter to drift and nothing to update. Spending the carrier destroys it, and a destroyed output has no height to subtract from. That is the entire reset mechanism. It is inherited from the UTXO model, not enforced by anyone.

The tier ladder. Raw covers every depth below 1 008.
IndexNameThreshold in blocksNominal time
0Raw0from the reveal
1Sheen1 008about 7 days
2Cast4 032about 28 days
3Verdigris12 960about 90 days
4Umber26 280about 6 months
5Bronze52 560about 1 year
6Oxide105 120about 2 years
7Elder210 000just under 4 years

Nominal times assume ten minutes per block. Thresholds are counted in blocks, so they do not move when block times do. The tier page carries the same ladder with the material each name comes from.

02 Marker grammar

The marker is one OP_RETURN output

script shape
OP_RETURN PUSH(n) [ "PTNA" | version(1) | op(1) | payload ]
  • The push must be a single minimal data push. Multiple pushes, non minimal pushes, or extra opcodes make the marker inert. It still occupies the marker slot of its transaction, so a malformed marker cannot be hidden behind a well formed one further along.
  • The protocol marker of a transaction is the OP_RETURN output with the lowest vout index whose payload starts with PTNA.
  • If more than one output carries a payload starting with PTNA, the marker is void with reason VOID_DUPLICATE_MARKER and the default rule applies as if no marker were present.
  • The whole scriptPubKey stays at or below 83 bytes.

SEED, opcode 0x01

SEED marker push, 24 bytes. Offsets are inside the push, not inside the script.
OffsetLengthFieldValueMeaning
04magic50 54 4e 41ASCII PTNA
41version01Marker version. Anything else is MARKER_UNKNOWN_VERSION.
51op01SEED, create an artifact.
616saltrandomThe same salt that went into the commitment.
221flags00Reserved at marker version 1. Every bit has to be zero, and a non zero byte is SEED_BAD_GRAMMAR.
231carrier_voutindexWhich output of this transaction becomes the carrier.
example scriptPubKey, 26 bytes
6a 18 50 54 4e 41 01 01 3f 8a 1c 5d 9e 04 b7 22 6c e1 40 93 aa 57 db 08 00 00

6a          OP_RETURN
18          push 24 bytes
50544e41    "PTNA"
01          version
01          SEED
3f8a...db08 16 byte salt, this one is only an example
00          flags
00          carrier is output 0

KEEP, opcode 0x02

KEEP marker push, 7 + 2n bytes for n entries, n from 1 to 8.
OffsetLengthFieldMeaning
04magicASCII PTNA
41versionAlways 01. Anything else is MARKER_UNKNOWN_VERSION.
51opAlways 02, KEEP.
61countNumber of entries, 1 to 8. Anything else is KEEP_BAD_GRAMMAR.
7 + 2i1input_index of entry iWhich input of this transaction is the carrier being routed.
8 + 2i1vout of entry iWhich output that carrier continues on.
example, two entries, 13 byte script
6a 0b 50 54 4e 41 01 02 02 00 01 01 03

6a          OP_RETURN
0b          push 11 bytes
50544e41    "PTNA"
01          version
02          KEEP
02          two entries
00 01       input 0 continues on output 1
01 03       input 1 continues on output 3

03 Claim safety

A commit only the claimant can open

A qualifying commit is a taproot output whose spend reveals the reduced-data leaf below or its permanent legacy encoding.

commit leaf
<claimant_xonly(32)> OP_CHECKSIG PUSH32(commitment) OP_DROP
The reduced-data leaf script, 68 bytes.
OffsetLengthBytesMeaning
0120Push 32 bytes.
132claimant keyThe claimant x only public key.
331acOP_CHECKSIG. Only the claimant can spend this leaf.
34120Push 32 bytes.
3532commitmentSHA-256 over the tag, the claimant key and the salt.
67175OP_DROP removes the commitment after authorization.

The signature check makes the claim unstealable. Pushing and dropping the commitment afterwards leaves that result unchanged and executes no conditional opcode.

The legacy 70 byte OP_0 OP_IF ... OP_ENDIF leaf stays parseable forever, so commits already confirmed under it are never stranded. New construction does not use it, because reaching an OP_IF at all is what BIP-110 rules out.

The leaf must be revealed in the SEED transaction's own witness, as a script path spend.

04 Transaction shapes

What each transaction has to contain

A claim is two Bitcoin transactions with at least 144 blocks between them. The gap is what stops a watcher of the mempool from acting on what they see.

A claim across two transactions A commit transaction publishes one taproot output holding the commit leaf and carries no marker. At least 144 blocks later a reveal transaction spends that output by script path, carries the SEED marker, and names the carrier. Depth starts counting at the reveal height. Commit transaction One taproot output holding the commit leaf. It carries no marker. Reveal transaction, the SEED Spends the commit by script path, carries the marker, names the carrier. COMMIT HEIGHT REVEAL HEIGHT AT LEAST 144 BLOCKS Depth starts at zero at the reveal height.
Nothing about the commit output says PATINA. It looks like any other taproot output until the reveal spends it.

Commit transaction

  • Any inputs that fund it.
  • One taproot output whose script tree contains the commit leaf above.
  • Change is allowed. Nothing else is required.
  • It carries no PATINA marker and means nothing on its own.

Reveal transaction, the SEED

  • Exactly one input must reveal a qualifying commit leaf by script path, and its commitment must match the salt in the marker payload and the leaf's claimant key.
  • The reveal height minus the commit output's creation height must be at least 144.
  • One OP_RETURN output carrying the SEED marker.
  • The output at carrier_vout must exist, must not be an OP_RETURN, and must hold at least the minimum for its era.

The SEED checks, in order

The marker has to decode as a SEED before any of this runs. A push of the wrong length, a push that is not minimal, a trailing byte, or a non zero flags byte is SEED_BAD_GRAMMAR and the transaction creates nothing.

Run per transaction, in block order. The first failure decides the code. The transaction then creates nothing, and an invalid event is recorded.
StepCheckReason code on failure
1carrier_vout is inside the output range.SEED_CARRIER_OUT_OF_RANGE
2The carrier output does not start with OP_RETURN.SEED_CARRIER_IS_OPRETURN
3At least one input reveals a commit leaf.SEED_NO_COMMIT_INPUT
4At least one revealed leaf commits to this salt and claimant key.SEED_COMMITMENT_MISMATCH
5No more than one revealed leaf qualifies.SEED_NO_COMMIT_INPUT
6The commit output is at least 144 blocks old.SEED_COMMIT_TOO_YOUNG
7Founding is decided, which selects the carrier minimum.nothing, this step cannot fail
8The carrier holds at least that minimum, 100 000 sats when founding and 10 000 otherwise.SEED_CARRIER_BELOW_MIN

An artifact is founding when its commit output was created inside the window, meaning h_open is less than or equal to the commit height and the commit height is below h_close, and the reveal lands at or before h_close plus the 4032 block grace period.

The mint page walks the same two transactions from the claimant's side.

05 Movement

Where an artifact goes when its carrier is spent

Any confirmed spend of a carrier closes the stretch. The record gains a ring, and the artifact either continues on a successor output at depth zero, or ends.

Successor selection When a carrier is spent, a valid KEEP entry naming that input routes the artifact to the output it names. If there is no valid entry, the default rule picks the lowest index output that is not an OP_RETURN and holds at least ten thousand satoshis. If neither yields an output, the artifact becomes a relic. A carrier is spent Is there a valid KEEP entry naming this input? MAX 8 ENTRIES PER MARKER YES NO OR VOID Route to the vout it names. Out of range, an OP_RETURN, or below 10 000 sats voids the entry. Default rule. Lowest index output that is not an OP_RETURN and holds at least 10 000 sats. A VOID ENTRY FALLS THROUGH An eligible output continues the artifact at depth zero. Otherwise the artifact ends here as a relic.
A relic is terminal. The rings it already engraved stay in the record forever, and no further stretch can be added.

The ring that gets engraved

One ring per completed stretch.
FieldValue
indexPosition in the artifact's ring list, starting at zero.
start_heightThe height at which the carrier was created.
end_heightThe height of the block that spent it.
depthend_height minus start_height. The depth the stretch reached.
carried_valueThe value the carrier held, in satoshis.
successor_txid, successor_voutWhere the artifact went, or null.
relicTrue when nothing eligible was found and the artifact ended here.

Rings are appended and never edited. An artifact at depth 40 today can still carry a ring from a stretch of four years, so a low depth now says nothing about what that artifact has already done. Depth and rings, in full.

Bundles move together

Several artifacts can share one carrier output. One input cannot be routed to two different outputs, so a bundle cannot be split inside a single transaction. Splitting means spending the carrier once per destination, across separate transactions, and each of those spends closes a stretch and engraves a ring on every artifact riding that carrier.

06 Derivations

Every identifier is a single SHA-256

byte exact, tags are ASCII with no separator
commitment  = SHA256("PTNA/commit"   || claimant_xonly(32) || salt(16))
artifact_id = SHA256("PTNA/artifact" || reveal_txid_wire(32) || carrier_vout_le(4))
event_leaf  = SHA256("PTNA/event"    || canonical event encoding)
state_root  = SHA256("PTNA/state"    || canonical snapshot encoding)
attestation = "PTNA/attest" || artifact_id_hex || block_hash_hex

reveal_txid_wire is the txid in internal byte order, which is the reverse of the txid your block explorer shows you. Display ids are lowercase hex. The attestation message is signed off chain with BIP-322, which is how a holder shows that the key behind a carrier is still alive.

Two indexers that agree on the state root at a height agree on everything below it. Verify an artifact yourself shows how to compare them.

07 Reorgs

A reorg is a replay, not a repair

PATINA state is a pure function of the confirmed chain read in block order. There is no state that exists outside the blocks, so a reorg needs no special handling beyond replaying.

  • An indexer disconnects the orphaned blocks, discards every event they produced, and applies the new blocks in order.
  • An artifact created by a reveal that got orphaned stops existing until that reveal confirms again. If it confirms in a different transaction, its id changes too, because the id is derived from the reveal txid.
  • A ring closed by a spend that got orphaned reopens, and the stretch continues from the original carrier height.
  • Depth is never stored per block, so nothing has to be recomputed backwards. It is always the current height minus the carrier height.
  • Founding status can change across a reorg if the commit output's height moves across a window boundary. This is why the grace period exists and why claims near the edges deserve patience.

At six confirmations the reference implementation reports a PATINA fact as final, and below six it reports it as pending. That is a reporting convention, not a consensus rule. Bitcoin decides, and PATINA reads.

08 Frozen registry

Every rejection is recorded with a code

A rejected marker is never dropped in silence. The registry is frozen, and the order below is the registry order.

All 18 reason codes.
CodeMeaning
SEED_BAD_GRAMMARThe SEED payload did not decode.
SEED_NO_COMMIT_INPUTNo input revealed a qualifying commit leaf.
SEED_COMMITMENT_MISMATCHThe commitment did not match the key and salt.
SEED_COMMIT_TOO_YOUNGThe commit output was less than 144 blocks old.
SEED_CARRIER_OUT_OF_RANGEcarrier_vout pointed past the last output.
SEED_CARRIER_IS_OPRETURNcarrier_vout pointed at an OP_RETURN output.
SEED_CARRIER_BELOW_MINThe carrier held less than the minimum for its era.
KEEP_BAD_GRAMMARThe KEEP payload did not decode.
KEEP_NO_CARRIER_INPUTThe transaction spent no carrier at all.
KEEP_ENTRY_NOT_CARRIERAn entry named an input that is not a carrier.
KEEP_ENTRY_OUT_OF_RANGEAn entry named a vout that does not exist.
KEEP_ENTRY_IS_OPRETURNAn entry named an OP_RETURN output.
KEEP_ENTRY_BELOW_MINAn entry named an output below 10 000 sats.
KEEP_DUPLICATE_INPUTTwo entries named the same input.
VOID_DUPLICATE_MARKERMore than one output carried a PTNA payload.
MARKER_UNKNOWN_OPThe op byte is not SEED or KEEP.
MARKER_UNKNOWN_VERSIONThe version byte is not recognised.
MARKER_TOO_LARGEThe marker script exceeded the size ceiling.

A void KEEP entry does not void the whole marker. It falls through to the default rule for that carrier alone.

09 Deployments

One record binds the numbers to a network

The tier ladder, the marker grammar and the entry ceiling are frozen in the specification. The window heights and the carrier minimums arrive through a deployment record instead. An indexer that loads a different record produces a different history from the same blocks, which is why the record is part of the input and not part of the code.

Network
The chain the record applies to. Regtest and signet deployment records ship in the repository.
Protocol id
Always PTNA. A record naming anything else is refused.
Specification hash
The SHA-256 of the specification file this record was written against, as 64 lowercase hex characters.
h_open and h_close
The founding window. A commit output created at or after h_open and below h_close is a founding commit.
Grace end
h_close plus the 4032 block grace period. A founding reveal has to land at or before it.
Carrier minimums
100 000 sats while founding, 10 000 sats afterwards. Both are stored in the record rather than assumed.
Commit age
The 144 blocks a commit output has to age before a reveal can spend it. Stored in the record, and required to equal the frozen constant.
Approvers
The names behind a mainnet activation. Optional on the test networks, and required on mainnet.

Mainnet is not set

The mainnet record ships with null heights and no approvers. Loading a mainnet record is refused unless the caller passes an explicit activation authorisation and the record names at least two approvers, so the code fails closed rather than guessing. The transparency page explains who holds that authority.

10 Go deeper

Where the normative text lives

Each page below takes one section of this summary and states it in full, with the edge cases this page had to leave out.