Chapter Βʹ · the UTXO model

Architecture

STYX has no database, no contract storage, and no global state machine. State lives in taproot addresses; authority lives in a one-unit token; and the covenants recognize each other through a dependency graph that is deliberately a DAG.

1State lives in the address

A vault is a taproot UTXO whose address commits both the covenant and the position's state:

vault address = taproot(
    internal key = NUMS,                    // unspendable point
    tapbranch(
        leaf( VAULT_CMR ),                    // the covenant program
        leaf_0xc4( OP_RETURN || debt(8 BE) || owner(32) || last_height(4 BE) )
    )                                        // 45-byte data leaf
)

The second leaf is pure data: 45 bytes beginning with OP_RETURN, so it is unspendable, and the covenant leaf is the only spend path vault.simf:8. The covenant reconstructs its own scriptPubKey from the witnessed state (jet::script_cmr, jet::internal_key, and a hand-hashed data leaf) and asserts it equals jet::current_script_hash() vault.simf:244. A wrong witnessed debt produces a different address, so witnessed state is bound to committed state: the state is self-authenticating. The issuer uses the same trick with a 5-byte leaf, OP_RETURN || last_mint_height(4 BE) issuer.simf:14-15.

Why state-in-address

With one shared pot and many vaults, each vault must carry its own debt. Committing it in the address lets a counterparty covenant check that a newly opened vault sits at exactly the address encoding that debt - otherwise OBOL could be minted against a vault the protocol could not later liquidate. A separate "state UTXO" was rejected because it creates a commitment cycle: the vault would commit the state covenant's CMR while the state covenant commits the vault's, and neither can be compiled first.

State changes are expressed as recursion: the covenant requires output 0 to be a vault address reconstructed with the new state (recurse_to, vault.simf:195-198). Debt is static between events; there is nothing to update lazily and no interest to accrue, which is what makes the address model workable.

2No key path exists

Every covenant UTXO uses the canonical BIP341 nothing-up-my-sleeve point as its taproot internal key:

NUMS = 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0

No one knows a discrete log for this point, so the key-path spend is impossible and the script path - the covenant - is the only way to move the UTXO. The deploy preflight asserts the NUMS is exactly this constant (chapter Θʹ), because a "NUMS" with a known discrete log would let its holder bypass every covenant in the system. The audits treat "no key path" as load-bearing: a future key-path leaf that let the issuer token escape would silently unlock the entire pot.

3The dependency DAG

Covenants must recognize each other: the vault pays fees into the reserve, the reserve reads the pot, the issuer builds vaults. Each recognition is a compile-time dependency - to pin a sibling you need its identity, and the identity of a covenant is only known after it compiles. Mutual CMR reconstruction is therefore impossible (each CMR would need the other first), and the whole graph must be a DAG:

VAULT_CMR · reconstructs STABILITY_SPK POT_SPK POT_SPK POT_SPK STABILITY_SPK Issuer compiled last Vault compiled third Stability compiled second Pot compiled first, the root
The dependency DAG. A gold edge is a full CMR reconstruction (the issuer rebuilds vault addresses inside Simplicity). Blue edges are flat scriptHash pins. Compile order: POT, STABILITY, VAULT, ISSUER.

The graph is {ISSUER -> {VAULT, POT, STABILITY}, VAULT -> {POT, STABILITY}, STABILITY -> POT}. Two devices keep it acyclic:

  • Flat identities. Where two covenants must check each other, one direction uses a non-CMR identity. The pot and the reserve are constant addresses - single UTXOs whose scriptHash never changes - so anyone can pin them by the flat parameters POT_SPK and STABILITY_SPK without a compile dependency cycle. The issuer is recognized in the other direction by its token asset id, ISSUER_TOKEN_ID, a genesis fact that needs no compilation at all (this is how the pot's outflow leaf and the reserve's bad-debt arm gate on the issuer).
  • One reconstructor. Only the issuer rebuilds vault addresses (the single gold edge). Vaults are parameterized by their data leaf, so they have no flat handle; whoever must verify "this is a genuine vault" must reconstruct it, and that ability is deliberately concentrated in one covenant.

4The issuer singleton

Mint authority is a physical object: a unique one-unit token, issued at genesis with no reissuance token, held only by the issuer covenant. Every arm of the issuer ends by passing the token to its own successor address, pinning asset == ISSUER_TOKEN_ID and amount == 1 (recurse_token, issuer.simf:215-222). The consequences:

  • The token can never leave the covenant, never split, and never be duplicated - single-unit uniqueness is a covenant invariant, not a convention.
  • The pot releases OBOL only when this token is co-spent at the input adjacent to it pot_outflow.simf:37-39, so every mint is authorized by a genuine, health-checked issuer arm.
  • At most one issuer-authorized operation exists per transaction: mints and bad-debt attestations serialize through this one UTXO. This is an accepted throughput trade - the mint path is the only one that needs the issuer; liquidations, redemptions, and repayments bypass it, sharing the pot (and, where fees flow, the reserve) chains instead, and REFRESH touches no singleton at all.
Design history

An earlier cut used OBOL's own reissuance token as the authority object. That was a critical hole: a reissuance token is minting power, and whoever spent the issuer could have reissued unlimited OBOL. The identity token is a separate asset precisely so that no reissuance right exists anywhere.

5The delegated-amount rule

The one covenant-break class that survived early review, and the design rule that closed it, deserve their own section, because every guard pattern in the frozen source follows from it.

The failure shape: a covenant authorizes a value movement by reconstructing a successor address (or by gating on token presence) while delegating the amount to a sibling branch it assumes is co-running. If an attacker can substitute which branch of that sibling actually runs - a REFRESH standing in for a DRAW, a zero-amount DRAW standing in for an ATTEST, a decoy UTXO standing at the expected index - the value moves with no arm ever pinning it. The fresh-context audit round traced four critical findings to this single root cause.

The rule

Pin the amount where you authorize the movement; never delegate it to a branch that can be swapped. Where delegation is unavoidable, force the delegate: gate on the token, pin current_index, read the co-spent sibling at a relative index (current_index - 1), and back it with an independent structural invariant (for example: "the pot strictly grows" is only true of ATTEST, so requiring strict growth forces ATTEST regardless of index games).

You will see the resulting idiom throughout chapter Γʹ: every value-carrying read pins script and asset and amount, both ends of a pot move are pinned to POT_SPK, every covenant pins its own current_index, and zero-amount operations are rejected (principal > 0, d > 0) so no arm can run "for free" just to satisfy someone else's gate.