Normative specification

Every rule, numbered,
and traced to the frozen bytes.

This page restates the Tandem protocol as numbered, individually citable rules. It is a navigation layer, not a replacement. The authoritative artifact is tandem.md, and every rule below names the section of that file it comes from. Where this page and the raw bytes ever differ, the raw bytes win.

Protocol finalized. Mainnet is not active.

How to read this page

Conventions

MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, and MAY carry their conformance meaning. All integers are unsigned and little-endian unless a rule says otherwise. Arithmetic is exact integer arithmetic with bounds checks and MUST NOT wrap. A value is invalid if it does not fit its field, or if an intermediate needed for validation cannot be computed exactly.

Notation used throughout, from tandem.md section 2
NotationMeaning
u8, u16le, u32le, u64leUnsigned little-endian integers of 1, 2, 4, and 8 bytes
SHA256(x)The 32 raw output bytes of SHA256 over byte string x
HASH160(x)RIPEMD160(SHA256(x)), as 20 raw bytes
a || bByte concatenation
zeroNExactly N zero bytes
txid_wire32A transaction hash in Bitcoin wire serialization order
outpoint36txid_wire32 || vout_u32le
Two hash orders, one rule.

Displayed transaction and block hashes use the lowercase 64-character hexadecimal order shown by Bitcoin Core. Converting a displayed hash to a wire hash reverses the 32 bytes exactly once. A raw SHA256 digest such as spec_hash, a namespace commitment, an object key, or a content commitment is never byte-reversed.

Absent fixed-width values are all zero bytes, with two exceptions: an absent state_seq and an event with no marker both use 0xffffffff. In particular an absent outpoint is zero36, not a null txid followed by 0xffffffff.

Rules ID-1 to ID-6

1. Identity and binding

Tandem has no global registry. A deployment is pinned to exactly one network, one INIT transaction, and one specification digest. Change any of the three and you are looking at a different protocol, not a different version of this one.

ID-1

A deployment binding consists of exactly one Bitcoin network code, one configured INIT txid on that network, and the SHA256 digest of the raw bytes of the specification file. The binding is immutable for one protocol identifier. tandem.md section 3

ID-2

An implementation MUST NOT select an INIT by first-seen order, lowest txid, block order, or marker discovery. An alternate INIT txid defines an alternate protocol identifier even when its payload fields are identical. tandem.md section 3

ID-3

The network codes are fixed.tandem.md section 3

CodeLabel
0x00mainnet
0x01signet
0x02testnet4
0x03regtest
ID-4

The protocol identifier and the object display identifier are exactly: tandem.md section 3

tndm:<network_label>:<configured_init_txid_display>

tandem:<network_label>:<configured_init_txid_display>:<create_txid_display>:1

The trailing 1 is decimal vout 1, the carrier output.

ID-5

The namespace commitment binds network, INIT, and specification bytes into one 32-byte value. It can be derived before INIT validation, but only a valid confirmed configured INIT activates state under it. tandem.md section 3

Namespace commitment
namespace_commitment = SHA256(
  "TANDEM/NAMESPACE\0" ||   
  network_u8 ||             
  configured_init_txid_wire32 ||
  spec_hash32
)
ID-6

For a valid CREATE at vout 1, the binary object key is derived once and never changes. Binary object keys, not display identifiers, are used for equality, ordering, database keys, event roots, and object-state roots. tandem.md section 3

Object key
object_key = SHA256(
  "TANDEM/OBJECT\0" ||      
  namespace_commitment ||
  create_txid_wire32 ||
  uint32_le(1)
)
Both derivations are checkable right now.

The published golden corpus fixes a regtest binding and states the resulting namespace and object key. The pair check tool recomputes both from the raw inputs in your browser, and the vectors page lists the exact values.

Rules REC-1 to REC-9

2. Record format

A Tandem record is an OP_RETURN marker output plus the exact transaction shape around it. The marker alone is never sufficient, and a malformed marker is deliberately still detectable so that it cannot hide.

2.1 Marker candidate detection

REC-1

An output is a Tandem marker candidate when all of the following can be established from its script bytes: the first opcode is OP_RETURN (0x6a); the next opcode is a direct data push from 0x01 through 0x4b or one of OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4; the declared pushed data is at least four bytes; at least the first four declared data bytes are physically present; and those four bytes are 54 4e 44 4d, ASCII TNDM. tandem.md section 6.1

REC-2

Candidate status deliberately does not require a minimal push, a complete declared payload, a known marker format, a known opcode, an exact operation length, zero output value, or the absence of trailing script bytes. Those are validation conditions. The broad candidate rule makes malformed markers deterministic and stops a second malformed marker from evading MULTIPLE_MARKERS. tandem.md section 6.1

REC-3

If byte offset 6 of a candidate payload is present and equals 0x00, the candidate is an INIT candidate. An INIT candidate in any transaction other than the configured INIT txid is foreign to this protocol identifier and is removed before marker counting. It emits no event by itself. If that transaction also spends an active carrier, processing continues as though the foreign candidate were absent. tandem.md section 6.1

2.2 Valid marker script

REC-4

A valid marker output has value zero, and its script contains exactly OP_RETURN, one minimal data push, and one exact payload, with no trailing opcode or byte. Every valid operation has exactly one marker candidate, at vout 0, and no other OP_RETURN output. tandem.md sections 6.2

REC-5

These are the only valid encodings.tandem.md section 6.2

Exact script encodings. Every value below is reproduced by the published golden markers.
OperationPayload bytesScript prefixScript bytes
INIT596a 3b61
CREATE406a 2842
MARK786a 4c 4e81
ROTATE446a 2c46
CLOSE806a 4c 5083
REC-6

For a structurally readable candidate the parser first enforces, in order: a complete push-length prefix; no payload shorter than seven bytes or longer than 80 bytes; declared push length equal to the physically present payload length; no bytes after the pushed payload; minimal push encoding; and, if opcode byte 6 is defined, the exact payload length for that opcode regardless of the marker-format byte. Failure of any item is BAD_MARKER_ENCODING_OR_LENGTH. A structurally valid payload with an undefined opcode may be any length from 7 through 80 and reaches marker-format and opcode validation. tandem.md section 6.2

2.3 Payload grammar

REC-7

All five payloads share the same first seven bytes: magic TNDM, marker format 0x01, the bound network code, and the opcode. tandem.md section 7

Common header, byte ranges are [start, end)
RangeSizeFieldRequired value
[0,4)4magicTNDM, hex 54 4e 44 4d
[4,5)1marker format0x01
[5,6)1networkbound network code
[6,7)1opcode0x00 to 0x04
REC-8

The remaining payload fields are exact.tandem.md sections 7.1 to 7.5

Operation-specific payload fields after the common seven-byte header
OperationRangeSizeFieldRequired value
INIT[7,11)4H_openvalidated by OP-1
[11,15)4H_closeH_open + 4320
[15,23)8carrier_value20,000
[23,27)4refund_delay52,560
[27,59)32spec_hashSHA256 of the exact specification bytes
CREATE[7,8)1state_vout0x01
[8,40)32namespaceconfigured namespace commitment
MARK[7,8)1state_vout0x01
[8,40)32namespaceconfigured namespace commitment
[40,44)4state_seqpredecessor sequence plus one
[44,46)1 + 1kind, flagsa defined kind; flags 0x00
[46,78)32chapter commitmentany nonzero 32-byte value
ROTATE[7,8)1state_vout0x01
[8,40)32namespaceconfigured namespace commitment
[40,44)4state_seqpredecessor sequence plus one
CLOSE[7,8)1state_vout0xff
[8,40)32namespaceconfigured namespace commitment
[40,44)4state_seqpredecessor sequence plus one
[44,48)1 + 3reason, reserveda defined reason; reserved 00 00 00
[48,80)32close commitmentany 32-byte value, including zero

REFUND has no marker and no payload. CREATE and ROTATE never duplicate keys in the payload: the keys are the ones revealed by their inputs.

REC-9

The MARK kinds and CLOSE reasons are closed sets.tandem.md section 4

MARK kinds

  • 0x00 note
  • 0x01 image
  • 0x02 audio
  • 0x03 milestone
  • 0x04 link
  • 0x05 opaque data

CLOSE reasons

  • 0x00 mutual completion
  • 0x01 relationship ended
  • 0x02 migrate outside Tandem
  • 0x03 other

No other opcode, kind, reason, flag, marker format, network byte, state vout value, or reserved value is defined in Tandem.

Rules PAIR-1 to PAIR-7

3. Pairing rules

This is the section that makes Tandem what it is. Every rule here describes a requirement that one half cannot satisfy alone.

3.1 The key pair

PAIR-1

The two carrier keys MUST be valid, distinct, compressed secp256k1 points, each exactly 33 bytes beginning with 0x02 or 0x03, sorted by unsigned bytewise lexicographic order so that key0 < key1. Side A is always key0. Side B is always key1. tandem.md section 5.2

PAIR-2

The witness script is exactly this, and exactly 71 bytes. Its native P2WSH scriptPubKey is OP_0 PUSH32 SHA256(witnessScript), 34 bytes serialized. tandem.md section 5.2

Carrier witness script
OP_2 PUSH33 key0 PUSH33 key1 OP_2 OP_CHECKMULTISIG

52 21 <key0_33> 21 <key1_33> 52 ae
PAIR-3

A carrier input has an empty scriptSig and exactly four witness elements, in this order. The first element MUST be empty. The first signature MUST verify for key0 and the second for key1. tandem.md section 5.2

Carrier witness stack
<empty> <signature_for_key0_plus_01> <signature_for_key1_plus_01> <witnessScript>

Both signatures MUST be strict DER and low-S excluding the final sighash byte, MUST end in 0x01 for SIGHASH_ALL, and MUST verify under SegWit v0 signature hashing using the exact prevout amount.

PAIR-4

A required native P2WPKH input has an empty scriptSig and exactly two witness elements, <DER_signature_plus_01> <compressed_public_key>. The key's HASH160 MUST equal the witness program in the spent prevout. tandem.md section 5.1

3.2 Pairing across time

PAIR-5

MARK, ROTATE, and CLOSE MUST carry a marker state_seq exactly equal to the consumed predecessor's sequence plus one. A predecessor sequence of 0xffffffff cannot be incremented and fails. REFUND and a terminal EXITED_NONCANONICAL event do not increment, so either may terminate a carrier that is already at 0xffffffff. tandem.md sections 9.3, 9.4, 9.5, 11.3

PAIR-6

A MARK MUST reproduce the predecessor carrier scriptPubKey byte for byte at vout 1. A ROTATE MUST place at vout 1 the exact carrier P2WSH derived from the successor pair. A ROTATE successor pair MUST differ from the old pair in at least one key, and MUST itself satisfy PAIR-1. tandem.md sections 9.3, 9.4

PAIR-7

CLOSE and REFUND MUST pay key0 and key1 at native P2WPKH outputs whose values are exactly equal and strictly positive. Neither side can end the object on better terms than the other. tandem.md sections 9.5, 9.6

3.3 Fee symmetry

Fees are split between the two sides by rule rather than by negotiation, using an exact ceiling and floor so that an odd fee is still deterministic.

Exact fee arithmetic per operation, from tandem.md sections 9.2 to 9.6
OperationDefinitionsRequired equalities
CREATE D0 = input0 - output2, D1 = input1 - output3, F = D0 + D1 - 20000 F > 0, D0 = 10000 + ceil(F/2), D1 = 10000 + floor(F/2)
MARK F = sponsor_input - output2 F > 0. Carrier value is unchanged, so no carrier value pays the fee.
ROTATE D0 = input1 - output2, D1 = input2 - output3, F = D0 + D1 F > 0, D0 = ceil(F/2), D1 = floor(F/2)
CLOSE F = 20000 - output1 - output2 F > 0, output1 = output2, both strictly positive
REFUND F = 20000 - output0 - output1 F > 0, output0 = output1, both strictly positive
The asymmetry is one satoshi, and it always favours side A.

For an odd fee, ceil(F/2) exceeds floor(F/2) by one. Side A pays that satoshi on CREATE and ROTATE. This is a deliberate tie-break so that two independent implementations never disagree about which side rounds up.

Rules OP-1 to OP-6

4. Operation validity

Each operation has an exact transaction template. Every count below is exact: not minimum, not typical. There is no extra data, payment, marker, input, or output.

Shared template requirements. All operations use version 2, locktime 0.
OperationInputsOutputsInput sequencesVout 1
INIT120xffffffffP2WPKH to the vin 0 key, at least 1,000 sats
CREATE240xfffffffd20,000 sats to the carrier P2WSH
MARK230xfffffffd20,000 sats to the identical predecessor carrier script
ROTATE340xfffffffd20,000 sats to the successor carrier P2WSH
CLOSE130xfffffffdP2WPKH to current key0
REFUND120x0000cd50 (52,560)P2WPKH to current key1 at vout 1
OP-1

INIT

The configured INIT is valid only when its txid is the externally configured INIT txid and it is confirmed on the bound network; version 2, locktime 0; exactly one input and two outputs; vin 0 sequence 0xffffffff; vin 0 spends a native P2WPKH prevout confirmed in an earlier block, reveals its bound compressed key, and carries a valid SIGHASH_ALL signature; vout 0 is the exact zero-value INIT marker; vout 1 is native P2WPKH to the vin 0 key and at least 1,000 satoshis; the input value minus vout 1 is the complete fee and is positive; the marker network is the bound network; carrier_value is 20,000 and refund_delay is 52,560; spec_hash is SHA256 of the exact specification bytes; H_open + 4320 fits u32 and equals H_close; and INIT_confirmation_height + 1008 is computed without overflow and is at most H_open. tandem.md section 9.1

There is no in-place replacement. If the configured INIT is absent, orphaned, conflicted away, or invalid, this protocol identifier is not activated.

OP-2

CREATE

A CREATE is valid only when the configured INIT is valid and on the active chain; version 2, locktime 0; exactly two inputs and four outputs; both input sequences 0xfffffffd; each input spends a native P2WPKH prevout confirmed in an earlier block; the key revealed by vin 0 is key0 and by vin 1 is key1, satisfying PAIR-1; vout 0 is the exact zero-value CREATE marker; vout 1 is exactly 20,000 satoshis to the exact carrier P2WSH; vout 2 is native P2WPKH to key0 and vout 3 to key1, each at least 1,000 satoshis; there is no extra data, payment, marker, input, or output; the fee arithmetic in section 3.3 holds; and the CREATE confirmation height is at or above H_open. tandem.md section 9.2

A valid CREATE at height h creates sequence 0. It is founding exactly when H_open <= h < H_close. A valid CREATE at or after H_close creates an ordinary, nonfounding object. A CREATE before H_open is invalid.

OP-3

MARK

Version 2, locktime 0; exactly two inputs and three outputs, both sequences 0xfffffffd; vin 0 spends the one active predecessor carrier, confirmed in an earlier block, satisfying PAIR-3 with the current key pair; vin 1 spends one native P2WPKH fee-sponsor prevout confirmed in an earlier block, whose key equals current key0 or current key1; vout 0 is the exact zero-value MARK marker; the marker state_seq satisfies PAIR-5; the kind is defined, flags is zero, and the chapter commitment is not all zero; vout 1 is exactly 20,000 satoshis to a P2WSH script identical byte for byte to the predecessor carrier scriptPubKey; vout 2 is native P2WPKH to the sponsor key and at least 1,000 satoshis; and the sponsor fee is positive. tandem.md section 9.3

A valid MARK appends exactly one chapter, increments sequence by one, retains the current keys, and makes (txid,1) the active carrier.

OP-4

ROTATE

Version 2, locktime 0; exactly three inputs and four outputs, all sequences 0xfffffffd; vin 0 spends the one active predecessor carrier, confirmed in an earlier block, satisfying PAIR-3 with the old pair; vin 1 and vin 2 each spend a native P2WPKH prevout confirmed in an earlier block, revealing successor key0 and successor key1 respectively, satisfying PAIR-1 and PAIR-6; vout 0 is the exact zero-value ROTATE marker; state_seq satisfies PAIR-5; vout 1 is exactly 20,000 satoshis to the exact successor carrier P2WSH; vout 2 and vout 3 are native P2WPKH to successor key0 and successor key1, each at least 1,000 satoshis; and the fee arithmetic in section 3.3 holds. tandem.md section 9.4

A valid ROTATE increments sequence by one, replaces the current keys with the successor keys, preserves chapter count, and makes (txid,1) the active carrier.

OP-5

CLOSE

Version 2, locktime 0; exactly one input and three outputs; vin 0 sequence 0xfffffffd; vin 0 spends the one active predecessor carrier, confirmed in an earlier block, satisfying PAIR-3 with the current pair; vout 0 is the exact zero-value CLOSE marker; state_seq satisfies PAIR-5; the reason is defined and all three reserved bytes are zero; vout 1 is native P2WPKH to current key0 and vout 2 to current key1, with exactly equal, strictly positive values; and the remaining fee is positive. tandem.md section 9.5

A valid CLOSE increments sequence by one, creates no successor, retains the last current keys, and terminates the object with status CLOSED.

OP-6

REFUND

REFUND is recognized only through the markerless dispatch branch. Version 2, locktime 0; exactly one input and two outputs; no OP_RETURN output; vin 0 spends the one active carrier and satisfies PAIR-3 with the current pair; vin 0 sequence is exactly decimal 52,560, numeric value 0x0000cd50, serialized 50 cd 00 00, with the BIP68 disable and type flags clear; vout 0 is native P2WPKH to current key0 and vout 1 to current key1, with exactly equal, strictly positive values; the remaining fee is positive; and Bitcoin consensus relative-locktime rules consider the input mature in the containing block. tandem.md section 9.6

The relative delay starts at the confirmation height of the exact carrier being spent. Every confirmed MARK or ROTATE replaces the carrier and restarts the delay. A valid REFUND does not increment state_seq; it records the current sequence, creates no successor, retains the current keys, and terminates the object with status REFUNDED. Every REFUND validation failure collapses to the single stable reason BAD_REFUND_SHAPE_OR_MATURITY.

Confirmation provenance applies to every required input.

Every predecessor carrier and every noncarrier funding input required by a valid operation MUST have been confirmed in a block strictly earlier than the block containing the operation. A prevout created earlier in the same block fails this rule. Mempool presence, first-seen time, and transaction arrival order never establish confirmation provenance. tandem.md section 5.3

Rules ST-1 to ST-8

5. State transitions

Object statuses
ValueStatus
0x00ACTIVE
0x01CLOSED
0x02REFUNDED
0x03EXITED_NONCANONICAL
ST-1

The complete set of transitions is exactly this. There are no others.tandem.md section 10

no object --valid CREATE--> ACTIVE sequence 0
ACTIVE    --valid MARK----> ACTIVE sequence + 1, chapter_count + 1
ACTIVE    --valid ROTATE--> ACTIVE sequence + 1, successor keys
ACTIVE    --valid CLOSE---> CLOSED sequence + 1
ACTIVE    --valid REFUND--> REFUNDED, sequence unchanged
ACTIVE    --any other confirmed carrier spend--> EXITED_NONCANONICAL, sequence unchanged
Tandem object state machine A valid CREATE produces an ACTIVE object at sequence 0. MARK and ROTATE loop back to ACTIVE with the sequence incremented. CLOSE leads to CLOSED, REFUND to REFUNDED, and any other confirmed carrier spend to EXITED NONCANONICAL. All three terminal states have no successor operation. no object nothing on chain CREATE ACTIVE one carrier, one pair, sequence n MARK, sequence + 1, chapter + 1 ROTATE, sequence + 1, new pair CLOSE REFUND any other spend CLOSED sequence + 1, keys retained REFUNDED sequence unchanged EXITED_NONCANONICAL sequence unchanged, not chosen
Three ways out, only two of them deliberate. The dashed path is what happens when a confirmed transaction consumes the carrier without matching any operation template.
ST-2

Any confirmed transaction that consumes an active carrier but does not validate as one allowed operation MUST atomically terminate that object as EXITED_NONCANONICAL. The spend is not ignored, because the active UTXO no longer exists. If one transaction consumes several active carriers, every consumed object terminates. Tandem never combines, splits, or forks objects. tandem.md section 10

ST-3

Terminal objects never become active again except by a reorganization of the active chain that removes their terminal spend. CLOSED, REFUNDED, and EXITED_NONCANONICAL have no Tandem successor operation. tandem.md section 10

ST-4

For each object: the genesis outpoint is permanently its valid CREATE outpoint (create_txid,1); sequence starts at 0; MARK, ROTATE, and CLOSE require and record exactly predecessor sequence plus one; REFUND and a terminal EXITED_NONCANONICAL event both retain the predecessor sequence; an active object has exactly one current outpoint, one sorted current key pair, carrier value 20,000, and the greatest valid state sequence; a valid MARK alone increments chapter_count; a terminal object has no current outpoint, retains its last key pair and sequence, and records its terminal txid; and invalid no-state events never mutate an object or a counter. tandem.md section 10

ST-5

A valid CREATE is founding exactly when its confirmation height on the active chain lies in [H_open, H_close). Founding status is immutable while that CREATE remains at that height. A reorganization can remove the CREATE or change its height, and therefore its founding status. tandem.md section 10

ST-6

The three post-block counters are exactly:tandem.md section 10

  • founding_created: all valid CREATE objects on the active chain whose CREATE height is in [H_open, H_close), including terminal objects.
  • all_objects: every valid CREATE on the active chain at or after H_open, including founding, ordinary, active, and terminal objects.
  • active_objects: every object whose post-block status is ACTIVE.

CLOSE, REFUND, and a terminal EXITED_NONCANONICAL event reduce only active_objects. A reorganization may change any counter. Mempool transactions never change protocol state or counters.

ST-7

A conforming implementation MUST preserve every invariant in tandem.md section 15, including that one binary object key maps to exactly one valid CREATE outpoint and one status; a valid outpoint on the active chain is consumed at most once; a chapter is unique by (object_key, state_seq); a carrier represents one jointly controlled object and is counted once, not once per key; and founding status depends only on the CREATE confirmation height on the active chain. tandem.md section 15

ST-8

Given the same deployment binding, the same exact specification bytes, and the same blocks on the active chain, independent implementations MUST produce identical events, reason codes, state, counters, event roots, object-state roots, and chained roots at every height. This is the invariant the two-pipeline agreement procedure exists to test. tandem.md section 15, invariant 12

Rules INV-1 to INV-5

6. Invalidity conditions and precedence

Tandem does not merely reject bad transactions. It assigns each one exactly one stable reason code, chosen by a fixed precedence. Two conforming implementations must select the same code, not just the same verdict.

6.1 Top-level dispatch

INV-1

For each confirmed transaction in scope, perform these steps in order. tandem.md section 11.2

  1. Find all marker candidates by REC-1.
  2. Remove foreign INIT candidates by REC-3.
  3. If this is the configured INIT txid and no marker remains, emit one class 0 event with reason BAD_MARKER_ENCODING_OR_LENGTH, event type INVALID, event_index 0xffffffff, and stop. The INIT fails.
  4. Join every input prevout against active object state as it exists immediately before this transaction in block transaction order. The matches are the carrier spends.
  5. If more than one marker remains, stop ordinary validation. With no carrier spend, emit one class 0 MULTIPLE_MARKERS event. Otherwise emit one class 2 terminal event per carrier spend, all with reason MULTIPLE_MARKERS, and terminate those objects.
  6. If more than one active carrier is spent, emit one class 2 terminal event per carrier with reason MULTIPLE_CARRIERS and terminate them. This applies whether zero or one marker remains.
  7. If no marker remains and exactly one carrier is spent, inspect shape. With exactly one input and two outputs, dispatch only to REFUND recognition: a match emits REFUND, any failure emits one terminal BAD_REFUND_SHAPE_OR_MATURITY. Otherwise emit one terminal UNMARKED_CARRIER_SPEND.
  8. If no marker remains and no carrier is spent, emit no event.
  9. If exactly one marker remains, parse and validate it. The configured INIT txid requires opcode INIT; a different defined opcode is a fixed-field failure; an undefined opcode remains UNKNOWN_OPCODE.
  10. A valid operation emits its valid event and applies its exact state delta.
  11. An invalid single-marker transaction with no carrier spend emits one class 0 event and no state delta.
  12. An invalid single-marker transaction with one carrier spend emits one class 2 EXITED_NONCANONICAL event, records the selected validation reason, and terminates the consumed object.
INV-2

Dispatch steps 5 through 7 take precedence over numeric reason-code order. A transaction with multiple markers and multiple carriers therefore receives MULTIPLE_MARKERS, while a transaction with one or zero markers and multiple carriers receives MULTIPLE_CARRIERS. tandem.md section 11.2

6.2 Single-marker validation order

INV-3

Within a selected single-marker operation, evaluate safely testable conditions in ascending reason-code order and return the lowest failing code. A parser MUST NOT guess a field that cannot be decoded. A state-dependent comparison that cannot be made because no active predecessor exists is deferred to PREDECESSOR_NOT_ACTIVE; independent earlier checks remain testable. tandem.md section 11.3

Ordered validation groups. Evaluate ascending, return the lowest failure.
CodeValidation group
0x0002Push prefix, completeness, minimality, script termination, maximum size, exact known-opcode payload length
0x0003Marker format
0x0004Network
0x0005Opcode
0x0006Non-INIT namespace
0x0007Fixed payload fields, supported kind or reason, flags, reserved bytes, INIT constants and spec_hash, required INIT opcode at the configured txid
0x0010Transaction version and locktime
0x0011Input count, fixed input roles, input order, fixed sequences
0x0012Output count, fixed output roles, marker position, absence of another OP_RETURN output
0x0013Earlier-block confirmation provenance
0x0014Required prevout script types, scriptSig shapes, witness shapes, carrier witness script shape
0x0015Curve validity, distinctness, sorting, HASH160 binding, sponsor membership, current carrier binding, ROTATE pair difference
0x0016Signature validity, signature order, strict DER, low-S, SIGHASH_ALL
0x0017Marker output value, fixed carrier value, CREATE carrier script, non-successor change or payout scripts and destinations
0x0018Positive exact fee and checked fee arithmetic
0x0019Participant fee split, equal payout, sponsor arithmetic, 1,000-satoshi change floors
0x001aActive predecessor existence on the active chain
0x001bExact state sequence increment and overflow prevention
0x001cMARK successor script preservation or ROTATE successor derivation
0x001dNonzero MARK commitment
0x001eINIT height relations or CREATE opening height
INV-4

Three tie-breaks are called out explicitly. For MARK and ROTATE, a vout 1 amount other than 20,000 is BAD_OUTPUT_SCRIPT_OR_VALUE; only once the amount is valid does a wrong successor script become BAD_SUCCESSOR. For CREATE, a wrong vout 1 carrier script is BAD_OUTPUT_SCRIPT_OR_VALUE. CLOSE and REFUND equality failures are fee-split failures, assigned after their output destinations and positive fee are established. tandem.md section 11.3

6.3 The stable reason registry

INV-5

This registry is exhaustive. Names and numeric values are permanent within Tandem. VALID is used only for a recognized valid operation. tandem.md section 12

All 26 stable reasons. Every one is exercised by the published golden corpus.
CodeStable nameMeaning
0x0000VALIDExact recognized INIT, CREATE, MARK, ROTATE, CLOSE, or REFUND
0x0001MULTIPLE_MARKERSMore than one remaining Tandem marker candidate
0x0002BAD_MARKER_ENCODING_OR_LENGTHNonminimal push, malformed script, incomplete push, trailing script data, oversized payload, or wrong exact payload length
0x0003UNKNOWN_MARKER_FORMATMarker format is not 0x01
0x0004WRONG_NETWORKMarker network differs from the bound chain
0x0005UNKNOWN_OPCODEOpcode is not defined for Tandem
0x0006WRONG_NAMESPACEA non-INIT namespace commitment differs from the configured INIT namespace
0x0007UNSUPPORTED_OR_RESERVED_FIELDA fixed field, constant, supported kind or reason, flag, reserved byte, configured INIT opcode, or INIT spec_hash differs
0x0010BAD_TX_VERSION_OR_LOCKTIMETransaction version or locktime differs from the exact template
0x0011BAD_INPUT_COUNT_OR_ORDERInput count, position, role, or required sequence differs
0x0012BAD_OUTPUT_COUNT_OR_ORDEROutput count, position, role, marker position, or only-OP_RETURN requirement differs
0x0013UNCONFIRMED_OR_SAME_BLOCK_PREVOUTA required prior output was not confirmed in an earlier block
0x0014BAD_INPUT_SCRIPTA prevout, scriptSig, witness stack, or revealed witness script has the wrong required form
0x0015BAD_KEY_ORDER_OR_BINDINGA key is invalid, duplicate, unsorted, unchanged when rotation is required, or not bound to its required input, output, sponsor, or carrier role
0x0016BAD_SIGNATURE_OR_SIGHASHA required signature is invalid, wrongly ordered, non-DER, high-S, or not SIGHASH_ALL
0x0017BAD_OUTPUT_SCRIPT_OR_VALUEMarker value, fixed carrier value, CREATE carrier script, or a required non-successor destination script or key differs
0x0018NONPOSITIVE_OR_INVALID_FEEFee is zero, negative, overflowed, underflowed, or cannot be computed exactly
0x0019BAD_FEE_SPLIT_OR_CHANGEA debit split, equal payout, sponsor change equation, or 1,000-satoshi change floor differs after fee positivity is established
0x001aPREDECESSOR_NOT_ACTIVEThe operation does not reference the active predecessor on the active chain required for it
0x001bBAD_STATE_SEQUENCEState sequence is not predecessor plus one or cannot be incremented
0x001cBAD_SUCCESSORMARK does not preserve the carrier script or ROTATE does not derive the proposed successor carrier script
0x001dBAD_COMMITMENTMARK chapter commitment is all zero
0x001eBAD_HEIGHT_OR_PHASEINIT lead or window relation fails, arithmetic overflows, or CREATE confirms before H_open
0x001fBAD_REFUND_SHAPE_OR_MATURITYA markerless one-carrier, one-input, two-output candidate fails any exact REFUND rule
0x0020MULTIPLE_CARRIERSA transaction spends more than one active Tandem carrier
0x0030UNMARKED_CARRIER_SPENDAn active carrier is spent without a remaining Tandem marker and the transaction is not dispatched as REFUND

6.4 Event types and validity classes

EVENT TYPE

  • 0x00 INIT
  • 0x01 CREATE
  • 0x02 MARK
  • 0x03 ROTATE
  • 0x04 CLOSE
  • 0x05 REFUND
  • 0x06 EXITED_NONCANONICAL
  • 0x07 INVALID

VALIDITY CLASS

  • 0x00 INVALID_NO_STATE
  • 0x01 VALID_OPERATION
  • 0x02 TERMINAL_NONCANONICAL

A class 0 event uses the attempted operation type only when exactly one marker exposes marker format 0x01 and a defined opcode. Otherwise it uses type INVALID. A class 2 event always uses type EXITED_NONCANONICAL, even when its reason came from an attempted marked operation.

Rules ROOT-1 to ROOT-4

7. Authoritative roots

Roots are how two implementations compare their whole view of history in 32 bytes. They use only SHA256 and fixed-width encodings, and only confirmed state from the active chain. Mempool observations are never included.

ROOT-1

Each event contributes one leaf. Order leaves by the tuple (tx_index, event_index, sub_index), comparing each unsigned integer numerically. tandem.md sections 13.2, 14.1

Event leaf preimage
SHA256(
  "TANDEM/EVENT\0" ||        
  namespace32 || block_hash_wire32 || height_u64le ||
  tx_index_u32le || event_index_u32le || sub_index_u32le ||
  event_type_u8 || validity_class_u8 || reason_u16le ||
  txid_wire32 || wtxid_wire32 || object_key32 ||
  state_seq_u32le || predecessor_outpoint36 || successor_outpoint36 ||
  key0_33 || key1_33 || commitment32
)

The field block after the domain tag is exactly 358 bytes. See the errata for the total preimage length.

ROOT-2

After applying all transactions in a block, every surviving object contributes exactly one snapshot leaf. Order object leaves by unsigned bytewise lexicographic object_key32. tandem.md section 14.2

Object-state leaf preimage, 204 bytes total
SHA256(
  "TANDEM/OBJECT-STATE\0" || 
  object_key32 || founding_u8 || status_u8 ||
  create_height_u64le || state_seq_u32le || current_outpoint36 ||
  key0_33 || key1_33 || terminal_txid_wire32 || chapter_count_u32le
)

Active objects use their active carrier as current_outpoint36 and zero32 as terminal txid. Terminal objects use zero36 as current outpoint, retain their last keys and sequence, and record the txid of CLOSE, REFUND, or the carrier spend that terminated it.

ROOT-3

Both Merkle trees duplicate an unpaired final hash at every level and hash it with itself. One leaf is its own root with no additional node hash. tandem.md sections 14.1, 14.2

Domain tags and their exact byte lengths, including the terminating zero byte
PurposeDomain tagBytes
Namespace commitmentTANDEM/NAMESPACE\017
Object keyTANDEM/OBJECT\014
Chapter commitmentTANDEM/CHAPTER\015
Close commitmentTANDEM/CLOSE\013
Event leafTANDEM/EVENT\013
Event Merkle nodeTANDEM/EVENT-NODE\018
Empty event rootTANDEM/EVENT-EMPTY\019
Object-state leafTANDEM/OBJECT-STATE\020
Object Merkle nodeTANDEM/OBJECT-NODE\019
Empty object rootTANDEM/OBJECT-EMPTY\020
Pre-INIT state rootTANDEM/STATE-EMPTY\019
Chained block rootTANDEM/BLOCKROOT\017

An empty event set for a block hashes "TANDEM/EVENT-EMPTY\0" || namespace_commitment; an empty object set hashes "TANDEM/OBJECT-EMPTY\0" || namespace_commitment.

ROOT-4

The chained root links every block on the active chain from the configured INIT confirmation block onward, including blocks with no Tandem event. The root immediately before that block is SHA256("TANDEM/STATE-EMPTY\0" || namespace_commitment). tandem.md section 14.3

Chained block root preimage, 209 bytes total
R_h = SHA256(
  "TANDEM/BLOCKROOT\0" ||    
  namespace_commitment || R_prev || block_hash_wire32 || height_u64le ||
  event_root32 || object_state_root32 ||
  founding_created_u64le || all_objects_u64le || active_objects_u64le
)

R_h becomes R_prev for the next block on the active chain. Each counter is the post-block value. Connecting a block applies transactions in transaction-index order, records events, applies state deltas atomically, computes both component roots and counters, then computes the chained root. Disconnecting a block reverses its state mutations and roots in exact reverse order before a replacement branch is applied.

Known discrepancy

8. Errata

The specification bytes are frozen and hash-pinned, so an error in its prose cannot be corrected in place without changing spec_hash and therefore the protocol identity. One such error exists. It is documented here rather than silently repaired.

ERRATUM 1: the stated event-leaf preimage length is wrong.

tandem.md section 13.2 states that the preimage after the domain tag is exactly 358 bytes, that the domain tag is 16 bytes, and that the complete preimage is exactly 374 bytes. The 358-byte field block is correct. The other two numbers are not.

The domain tag TANDEM/EVENT\0 is 12 ASCII characters plus one zero byte, so it is 13 bytes. The complete preimage is therefore 371 bytes.

This is not a matter of interpretation. Every event preimage published in vectors/generated/golden.json is 371 bytes long, and hashing each one reproduces the published leaf. The independent Rust verifier asserts 371 bytes at the same point. Both implementations and the frozen vectors agree with each other and disagree with the prose sentence.

An implementation MUST follow the byte layout and the published vectors, which are unambiguous, and MUST NOT pad the preimage to 374 bytes.

You can confirm this yourself without trusting this page. The pair check tool hashes the published preimages in your browser and prints their length alongside the recomputed leaves, and the vectors page lists the same measurement.

Protocol boundary

Tandem is immutable. Unknown marker formats, opcodes, kinds, reasons, nonzero flags, nonzero reserved bytes, extension bytes, nonminimal pushes, and oversized payloads are invalid. A distinct protocol requires a different specification hash, INIT, namespace, and protocol identifier, and a parser for a distinct protocol MUST NOT reinterpret Tandem events or state. No Tandem rule grants an administrator, coordinator, content host, indexer, wallet, or issuer authority to change keys, sequence, commitments, founding status, object status, roots, or supply.