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 | Meaning |
|---|---|
u8, u16le, u32le, u64le | Unsigned 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 || b | Byte concatenation |
zeroN | Exactly N zero bytes |
txid_wire32 | A transaction hash in Bitcoin wire serialization order |
outpoint36 | txid_wire32 || vout_u32le |
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.
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
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
The network codes are fixed.tandem.md section 3
| Code | Label |
|---|---|
0x00 | mainnet |
0x01 | signet |
0x02 | testnet4 |
0x03 | regtest |
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.
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 = SHA256(
"TANDEM/NAMESPACE\0" ||
network_u8 ||
configured_init_txid_wire32 ||
spec_hash32
)
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 = SHA256(
"TANDEM/OBJECT\0" ||
namespace_commitment ||
create_txid_wire32 ||
uint32_le(1)
)
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
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
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
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
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
These are the only valid encodings.tandem.md section 6.2
| Operation | Payload bytes | Script prefix | Script bytes |
|---|---|---|---|
| INIT | 59 | 6a 3b | 61 |
| CREATE | 40 | 6a 28 | 42 |
| MARK | 78 | 6a 4c 4e | 81 |
| ROTATE | 44 | 6a 2c | 46 |
| CLOSE | 80 | 6a 4c 50 | 83 |
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
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
| Range | Size | Field | Required value |
|---|---|---|---|
[0,4) | 4 | magic | TNDM, hex 54 4e 44 4d |
[4,5) | 1 | marker format | 0x01 |
[5,6) | 1 | network | bound network code |
[6,7) | 1 | opcode | 0x00 to 0x04 |
The remaining payload fields are exact.tandem.md sections 7.1 to 7.5
| Operation | Range | Size | Field | Required value |
|---|---|---|---|---|
| INIT | [7,11) | 4 | H_open | validated by OP-1 |
[11,15) | 4 | H_close | H_open + 4320 | |
[15,23) | 8 | carrier_value | 20,000 | |
[23,27) | 4 | refund_delay | 52,560 | |
[27,59) | 32 | spec_hash | SHA256 of the exact specification bytes | |
| CREATE | [7,8) | 1 | state_vout | 0x01 |
[8,40) | 32 | namespace | configured namespace commitment | |
| MARK | [7,8) | 1 | state_vout | 0x01 |
[8,40) | 32 | namespace | configured namespace commitment | |
[40,44) | 4 | state_seq | predecessor sequence plus one | |
[44,46) | 1 + 1 | kind, flags | a defined kind; flags 0x00 | |
[46,78) | 32 | chapter commitment | any nonzero 32-byte value | |
| ROTATE | [7,8) | 1 | state_vout | 0x01 |
[8,40) | 32 | namespace | configured namespace commitment | |
[40,44) | 4 | state_seq | predecessor sequence plus one | |
| CLOSE | [7,8) | 1 | state_vout | 0xff |
[8,40) | 32 | namespace | configured namespace commitment | |
[40,44) | 4 | state_seq | predecessor sequence plus one | |
[44,48) | 1 + 3 | reason, reserved | a defined reason; reserved 00 00 00 | |
[48,80) | 32 | close commitment | any 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.
The MARK kinds and CLOSE reasons are closed sets.tandem.md section 4
MARK kinds
0x00note0x01image0x02audio0x03milestone0x04link0x05opaque data
CLOSE reasons
0x00mutual completion0x01relationship ended0x02migrate outside Tandem0x03other
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
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
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
OP_2 PUSH33 key0 PUSH33 key1 OP_2 OP_CHECKMULTISIG
52 21 <key0_33> 21 <key1_33> 52 ae
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
<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.
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
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
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
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.
| Operation | Definitions | Required 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 |
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.
| Operation | Inputs | Outputs | Input sequences | Vout 1 |
|---|---|---|---|---|
| INIT | 1 | 2 | 0xffffffff | P2WPKH to the vin 0 key, at least 1,000 sats |
| CREATE | 2 | 4 | 0xfffffffd | 20,000 sats to the carrier P2WSH |
| MARK | 2 | 3 | 0xfffffffd | 20,000 sats to the identical predecessor carrier script |
| ROTATE | 3 | 4 | 0xfffffffd | 20,000 sats to the successor carrier P2WSH |
| CLOSE | 1 | 3 | 0xfffffffd | P2WPKH to current key0 |
| REFUND | 1 | 2 | 0x0000cd50 (52,560) | P2WPKH to current key1 at vout 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.
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.
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.
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.
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.
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.
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
| Value | Status |
|---|---|
0x00 | ACTIVE |
0x01 | CLOSED |
0x02 | REFUNDED |
0x03 | EXITED_NONCANONICAL |
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
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
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
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
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
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 afterH_open, including founding, ordinary, active, and terminal objects.active_objects: every object whose post-block status isACTIVE.
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.
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
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
For each confirmed transaction in scope, perform these steps in order. tandem.md section 11.2
- Find all marker candidates by REC-1.
- Remove foreign INIT candidates by REC-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 typeINVALID,event_index0xffffffff, and stop. The INIT fails. - 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.
- If more than one marker remains, stop ordinary validation. With no carrier spend, emit one class 0
MULTIPLE_MARKERSevent. Otherwise emit one class 2 terminal event per carrier spend, all with reasonMULTIPLE_MARKERS, and terminate those objects. - If more than one active carrier is spent, emit one class 2 terminal event per carrier with reason
MULTIPLE_CARRIERSand terminate them. This applies whether zero or one marker remains. - 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 terminalUNMARKED_CARRIER_SPEND. - If no marker remains and no carrier is spent, emit no event.
- 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. - A valid operation emits its valid event and applies its exact state delta.
- An invalid single-marker transaction with no carrier spend emits one class 0 event and no state delta.
- An invalid single-marker transaction with one carrier spend emits one class 2
EXITED_NONCANONICALevent, records the selected validation reason, and terminates the consumed object.
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
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
| Code | Validation group |
|---|---|
0x0002 | Push prefix, completeness, minimality, script termination, maximum size, exact known-opcode payload length |
0x0003 | Marker format |
0x0004 | Network |
0x0005 | Opcode |
0x0006 | Non-INIT namespace |
0x0007 | Fixed payload fields, supported kind or reason, flags, reserved bytes, INIT constants and spec_hash, required INIT opcode at the configured txid |
0x0010 | Transaction version and locktime |
0x0011 | Input count, fixed input roles, input order, fixed sequences |
0x0012 | Output count, fixed output roles, marker position, absence of another OP_RETURN output |
0x0013 | Earlier-block confirmation provenance |
0x0014 | Required prevout script types, scriptSig shapes, witness shapes, carrier witness script shape |
0x0015 | Curve validity, distinctness, sorting, HASH160 binding, sponsor membership, current carrier binding, ROTATE pair difference |
0x0016 | Signature validity, signature order, strict DER, low-S, SIGHASH_ALL |
0x0017 | Marker output value, fixed carrier value, CREATE carrier script, non-successor change or payout scripts and destinations |
0x0018 | Positive exact fee and checked fee arithmetic |
0x0019 | Participant fee split, equal payout, sponsor arithmetic, 1,000-satoshi change floors |
0x001a | Active predecessor existence on the active chain |
0x001b | Exact state sequence increment and overflow prevention |
0x001c | MARK successor script preservation or ROTATE successor derivation |
0x001d | Nonzero MARK commitment |
0x001e | INIT height relations or CREATE opening height |
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
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
| Code | Stable name | Meaning |
|---|---|---|
0x0000 | VALID | Exact recognized INIT, CREATE, MARK, ROTATE, CLOSE, or REFUND |
0x0001 | MULTIPLE_MARKERS | More than one remaining Tandem marker candidate |
0x0002 | BAD_MARKER_ENCODING_OR_LENGTH | Nonminimal push, malformed script, incomplete push, trailing script data, oversized payload, or wrong exact payload length |
0x0003 | UNKNOWN_MARKER_FORMAT | Marker format is not 0x01 |
0x0004 | WRONG_NETWORK | Marker network differs from the bound chain |
0x0005 | UNKNOWN_OPCODE | Opcode is not defined for Tandem |
0x0006 | WRONG_NAMESPACE | A non-INIT namespace commitment differs from the configured INIT namespace |
0x0007 | UNSUPPORTED_OR_RESERVED_FIELD | A fixed field, constant, supported kind or reason, flag, reserved byte, configured INIT opcode, or INIT spec_hash differs |
0x0010 | BAD_TX_VERSION_OR_LOCKTIME | Transaction version or locktime differs from the exact template |
0x0011 | BAD_INPUT_COUNT_OR_ORDER | Input count, position, role, or required sequence differs |
0x0012 | BAD_OUTPUT_COUNT_OR_ORDER | Output count, position, role, marker position, or only-OP_RETURN requirement differs |
0x0013 | UNCONFIRMED_OR_SAME_BLOCK_PREVOUT | A required prior output was not confirmed in an earlier block |
0x0014 | BAD_INPUT_SCRIPT | A prevout, scriptSig, witness stack, or revealed witness script has the wrong required form |
0x0015 | BAD_KEY_ORDER_OR_BINDING | A key is invalid, duplicate, unsorted, unchanged when rotation is required, or not bound to its required input, output, sponsor, or carrier role |
0x0016 | BAD_SIGNATURE_OR_SIGHASH | A required signature is invalid, wrongly ordered, non-DER, high-S, or not SIGHASH_ALL |
0x0017 | BAD_OUTPUT_SCRIPT_OR_VALUE | Marker value, fixed carrier value, CREATE carrier script, or a required non-successor destination script or key differs |
0x0018 | NONPOSITIVE_OR_INVALID_FEE | Fee is zero, negative, overflowed, underflowed, or cannot be computed exactly |
0x0019 | BAD_FEE_SPLIT_OR_CHANGE | A debit split, equal payout, sponsor change equation, or 1,000-satoshi change floor differs after fee positivity is established |
0x001a | PREDECESSOR_NOT_ACTIVE | The operation does not reference the active predecessor on the active chain required for it |
0x001b | BAD_STATE_SEQUENCE | State sequence is not predecessor plus one or cannot be incremented |
0x001c | BAD_SUCCESSOR | MARK does not preserve the carrier script or ROTATE does not derive the proposed successor carrier script |
0x001d | BAD_COMMITMENT | MARK chapter commitment is all zero |
0x001e | BAD_HEIGHT_OR_PHASE | INIT lead or window relation fails, arithmetic overflows, or CREATE confirms before H_open |
0x001f | BAD_REFUND_SHAPE_OR_MATURITY | A markerless one-carrier, one-input, two-output candidate fails any exact REFUND rule |
0x0020 | MULTIPLE_CARRIERS | A transaction spends more than one active Tandem carrier |
0x0030 | UNMARKED_CARRIER_SPEND | An 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
0x00INIT0x01CREATE0x02MARK0x03ROTATE0x04CLOSE0x05REFUND0x06EXITED_NONCANONICAL0x07INVALID
VALIDITY CLASS
0x00INVALID_NO_STATE0x01VALID_OPERATION0x02TERMINAL_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.
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
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.
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
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.
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
| Purpose | Domain tag | Bytes |
|---|---|---|
| Namespace commitment | TANDEM/NAMESPACE\0 | 17 |
| Object key | TANDEM/OBJECT\0 | 14 |
| Chapter commitment | TANDEM/CHAPTER\0 | 15 |
| Close commitment | TANDEM/CLOSE\0 | 13 |
| Event leaf | TANDEM/EVENT\0 | 13 |
| Event Merkle node | TANDEM/EVENT-NODE\0 | 18 |
| Empty event root | TANDEM/EVENT-EMPTY\0 | 19 |
| Object-state leaf | TANDEM/OBJECT-STATE\0 | 20 |
| Object Merkle node | TANDEM/OBJECT-NODE\0 | 19 |
| Empty object root | TANDEM/OBJECT-EMPTY\0 | 20 |
| Pre-INIT state root | TANDEM/STATE-EMPTY\0 | 19 |
| Chained block root | TANDEM/BLOCKROOT\0 | 17 |
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.
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
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.
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.