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.
What you will know by the end
- Why depth is a subtraction and never a stored counter.
- The exact bytes of a SEED marker and a KEEP marker.
- The commit leaf, and why only the claimant can ever open it.
- The SEED checks in the order they run, and what each failure records.
- Where an artifact goes when its carrier is spent.
- What a reorg can change, and what it cannot.
The specification is the normative document. This page is a summary of it, not a replacement for it.
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 = 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.
| Index | Name | Threshold in blocks | Nominal time |
|---|---|---|---|
| 0 | Raw | 0 | from the reveal |
| 1 | Sheen | 1 008 | about 7 days |
| 2 | Cast | 4 032 | about 28 days |
| 3 | Verdigris | 12 960 | about 90 days |
| 4 | Umber | 26 280 | about 6 months |
| 5 | Bronze | 52 560 | about 1 year |
| 6 | Oxide | 105 120 | about 2 years |
| 7 | Elder | 210 000 | just 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
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 reasonVOID_DUPLICATE_MARKERand the default rule applies as if no marker were present. - The whole scriptPubKey stays at or below 83 bytes.
SEED, opcode 0x01
| Offset | Length | Field | Value | Meaning |
|---|---|---|---|---|
| 0 | 4 | magic | 50 54 4e 41 | ASCII PTNA |
| 4 | 1 | version | 01 | Marker version. Anything else is MARKER_UNKNOWN_VERSION. |
| 5 | 1 | op | 01 | SEED, create an artifact. |
| 6 | 16 | salt | random | The same salt that went into the commitment. |
| 22 | 1 | flags | 00 | Reserved at marker version 1. Every bit has to be zero, and a non zero byte is SEED_BAD_GRAMMAR. |
| 23 | 1 | carrier_vout | index | Which output of this transaction becomes the carrier. |
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
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 4 | magic | ASCII PTNA |
| 4 | 1 | version | Always 01. Anything else is MARKER_UNKNOWN_VERSION. |
| 5 | 1 | op | Always 02, KEEP. |
| 6 | 1 | count | Number of entries, 1 to 8. Anything else is KEEP_BAD_GRAMMAR. |
| 7 + 2i | 1 | input_index of entry i | Which input of this transaction is the carrier being routed. |
| 8 + 2i | 1 | vout of entry i | Which output that carrier continues on. |
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.
<claimant_xonly(32)> OP_CHECKSIG PUSH32(commitment) OP_DROP
| Offset | Length | Bytes | Meaning |
|---|---|---|---|
| 0 | 1 | 20 | Push 32 bytes. |
| 1 | 32 | claimant key | The claimant x only public key. |
| 33 | 1 | ac | OP_CHECKSIG. Only the claimant can spend this leaf. |
| 34 | 1 | 20 | Push 32 bytes. |
| 35 | 32 | commitment | SHA-256 over the tag, the claimant key and the salt. |
| 67 | 1 | 75 | OP_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.
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_voutmust 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.
| Step | Check | Reason code on failure |
|---|---|---|
| 1 | carrier_vout is inside the output range. | SEED_CARRIER_OUT_OF_RANGE |
| 2 | The carrier output does not start with OP_RETURN. | SEED_CARRIER_IS_OPRETURN |
| 3 | At least one input reveals a commit leaf. | SEED_NO_COMMIT_INPUT |
| 4 | At least one revealed leaf commits to this salt and claimant key. | SEED_COMMITMENT_MISMATCH |
| 5 | No more than one revealed leaf qualifies. | SEED_NO_COMMIT_INPUT |
| 6 | The commit output is at least 144 blocks old. | SEED_COMMIT_TOO_YOUNG |
| 7 | Founding is decided, which selects the carrier minimum. | nothing, this step cannot fail |
| 8 | The 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.
The ring that gets engraved
| Field | Value |
|---|---|
| index | Position in the artifact's ring list, starting at zero. |
| start_height | The height at which the carrier was created. |
| end_height | The height of the block that spent it. |
| depth | end_height minus start_height. The depth the stretch reached. |
| carried_value | The value the carrier held, in satoshis. |
| successor_txid, successor_vout | Where the artifact went, or null. |
| relic | True 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
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.
| Code | Meaning |
|---|---|
| SEED_BAD_GRAMMAR | The SEED payload did not decode. |
| SEED_NO_COMMIT_INPUT | No input revealed a qualifying commit leaf. |
| SEED_COMMITMENT_MISMATCH | The commitment did not match the key and salt. |
| SEED_COMMIT_TOO_YOUNG | The commit output was less than 144 blocks old. |
| SEED_CARRIER_OUT_OF_RANGE | carrier_vout pointed past the last output. |
| SEED_CARRIER_IS_OPRETURN | carrier_vout pointed at an OP_RETURN output. |
| SEED_CARRIER_BELOW_MIN | The carrier held less than the minimum for its era. |
| KEEP_BAD_GRAMMAR | The KEEP payload did not decode. |
| KEEP_NO_CARRIER_INPUT | The transaction spent no carrier at all. |
| KEEP_ENTRY_NOT_CARRIER | An entry named an input that is not a carrier. |
| KEEP_ENTRY_OUT_OF_RANGE | An entry named a vout that does not exist. |
| KEEP_ENTRY_IS_OPRETURN | An entry named an OP_RETURN output. |
| KEEP_ENTRY_BELOW_MIN | An entry named an output below 10 000 sats. |
| KEEP_DUPLICATE_INPUT | Two entries named the same input. |
| VOID_DUPLICATE_MARKER | More than one output carried a PTNA payload. |
| MARKER_UNKNOWN_OP | The op byte is not SEED or KEEP. |
| MARKER_UNKNOWN_VERSION | The version byte is not recognised. |
| MARKER_TOO_LARGE | The 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.
- Byte level Marker grammar Encoding and decoding rules field by field, with the invalid cases and what each one produces.
- Creation SEED rules The commit leaf, the age requirement, and every check with the exact condition it tests.
- Movement KEEP rules Entry validity, the default rule, bundles, and how a void entry falls through.
- Diagnostics Reason codes The frozen registry with the condition that emits each code.
- Another implementation Build a compatible indexer The steps in order, the golden vectors, and how to compare state roots against a reference run.
- SpecificationEvery rule in full, including the edge cases and the canonical encodings this page leaves out.
- Test vectorsEncode and decode cases, valid and invalid, for anyone writing another implementation.
- Verify it yourselfRead depth off your own node with copyable commands.
- What can go wrongThe failure modes this page describes, written from the holder's side.