Skip to content

Latest commit

 

History

History
165 lines (130 loc) · 8.78 KB

File metadata and controls

165 lines (130 loc) · 8.78 KB

Bitcoin is a state-transition machine; Script is the predicate

Everything in this bench is one idea, worked out forty-five ways and then mechanised. The idea is not that Bitcoin Script has a long list of features. It is that Script has almost none — a handful of small primitives — and that this is enough, because the primitives compose into an arbitrary boolean condition over the spending transaction and its witness.

Write it as a predicate. A UTXO holds a state S. A transaction proposes to replace it with a successor state S', carrying witness data W — signatures, parent transactions, oracle attestations, preimages, whatever the rule needs. The locking script is the function that decides whether that transition is allowed:

    Allowed( S, S', W )  →  TRUE | FALSE

TRUE, the coins move to S'. FALSE, they do not move at all. That is the whole machine. A contract is not a program that runs; it is a predicate that the next state must satisfy.


Every contract in this repo is a predicate

The catalogue looks like a list of unrelated applications — payments, timelocks, tokens, oracles, vesting. It is not. Each is the same Allowed(S, S', W) with a different body, and each was built from the same opcode primitives (stack moves, hashing, signatures, arithmetic, comparison, byte slicing, branching, and transaction introspection through the preimage):

the rule, in words the predicate where
only the holder of this key may spend HASH160(pubkey)==pkh ∧ checksig(sig,pubkey) p2pkh
reveal a value whose hash is X SHA256(w)==X hashlock
not spendable before time T preimage.nLockTime ≥ T ∧ input non-final timelock
pay exactly this output set preimage.hashOutputs == H covenant
the successor must be this same covenant HASH256(value ‖ selfChunk) == preimage.hashOutputs perpetual
the successor counter is mine + 1 S'.counter == S.counter + 1 metered
split conserves the balance S'₀.bal + S'₁.bal == S.bal token
both owners authorise a merge checksig(ownerA) ∧ checksig(ownerB) token/asset
this child descends from genesis G parent spent G ∨ parent was a valid token of G lineage
2 of 3 oracles attest the same value Σ rabinValid(sigᵢ, m, Nᵢ) ≥ 2 quorum
the new oracle round exceeds the stored one S'.round > S.round ticker
pay exactly the amount vested by time T S'.retained == total·(end−T)/(end−start) vesting

None of these are special Bitcoin features. They are predicates assembled out of primitives, and the assembling is now systematic — see the compiler.


The blockchain supplies the iteration

The obvious objection is that Script has no unbounded loop, no mutable memory, no way to run a process forward. It does not need one. A loop is a sequence of state updates; Bitcoin expresses that sequence as a chain of UTXOs, each spend the body of one iteration:

    UTXO(S₀) ──tx──▶ UTXO(S₁) ──tx──▶ UTXO(S₂) ──tx──▶ UTXO(S₃)
              Allowed          Allowed          Allowed

Instead of while (cond) { update(state) } running inside one program, each transition is a separate transaction that the covenant re-imposes on its own successor. This is not a workaround; it is the natural shape. A self-recreating covenant reads its own bytes out of the authenticated preimage and demands the next output carry the same script — so the predicate propagates itself forward, unchanged, hop after hop. Give one field permission to change and the loop has a body: metered advances a counter, ticker advances an oracle round, vesting releases value as a function of the block clock. The chain is the iteration, and each UTXO is one frame of the running machine.


The network is the inductive verifier

The second objection is depth: if S₁₀₀₀ must prove it descended legitimately from S₀, does the spender have to carry a thousand transactions? No — and lineage is the proof. A spend proves only its immediate transition, and relies on induction for the rest:

  • to move, Sₙ proves its parent Sₙ₋₁ satisfied the covenant one hop back;
  • but Sₙ₋₁ only exists on chain because its covenant ran and was accepted when it was spent — the network already checked that transition;
  • so proving one hop, plus the network's prior acceptance of every earlier hop, establishes the whole history back to the single spend of genesis G.

The witness is bounded — two parent transactions per spend, not O(n) — and the blockchain itself is the verifier that closes the induction. A counterfeit (the right bytes, minted from a plain UTXO) is byte-valid but unspendable, because its one funding hop never spent G and never will. Authenticity is not stored; it is re-proven, cheaply, on every move, and the network's own history is what makes that cheap.


What the witness carries

W is whatever evidence the predicate needs to check, supplied by the spender and verified — never trusted — by the script:

  • a signature, checked against this transaction's own sighash (p2pkh);
  • a preimage of this transaction, so the script can read its own outputs, value, and locktime (preimage.md);
  • a parent transaction, bound to its outpoint because a txid is its hash (token, lineage);
  • a sibling input, bound by hashPrevouts (cross-input.md);
  • an oracle attestation — a value signed off-chain, verified in-script with modular arithmetic because there is no OP_CHECKDATASIG (oracle.md).

The discipline that runs through all of it: the script must verify every piece of the witness, never accept it on faith. The sharpest lesson in the bench is a witness lie the naive design believed — a merge that trusts a pushed sibling balance mints money from nothing. The fix is to prove the sibling's balance by rebuilding its funding transaction and hashing to its txid. The compiler now refuses, at build time, any predicate that reads a sibling balance without that backtrace.


The honest limit

The claim is not that Script computes every computable predicate. It is deliberately bounded: no unbounded loops, no arbitrary mutable memory, no network calls, no clock beyond the block. Computation must fit inside one transaction's script evaluation. What lifts the ceiling is that so much of what looks like it needs a loop is really a structural property, expressible without one:

  • iteration → a chain of UTXOs (above);
  • deep history → bounded witnesses + inductive network verification (above);
  • large state → carried forward in scriptCode, one fixed-width field at a time (registry, sovereign);
  • "trust this external fact" → a signed attestation the script checks (oracle);
  • "trust this sibling's value" → a backtrace, not a push (token).

Within that envelope, the predicates get very expressive — the sovereign covenant conserves a balance, gates ownership, and proves descent to a genesis, over a divisible mint→split→merge lifecycle, in one 1420-byte script.


The reframing

Once Script is a predicate over a state transition, the question worth asking is not "what smart-contract features does Bitcoin have?" — it has none, and needs none. The question is:

What predicate must the next state satisfy? Then construct it from the primitives.

That is what this whole bench is: forty-five worked answers, each measured against the real consensus interpreter and most spent on mainnet, and a compiler that turns the construction into spec assembly — with the rules the bench paid for in real broadcasts (a covenant must commit to its whole transaction; a self-draining one needs an exit; conservation needs a backtrace; authenticity needs descent; ownership needs a signature) enforced before a byte is emitted.

Bitcoin is a distributed state-transition machine. UTXOs hold the state, transactions propose the transitions, and Script is the predicate a transition must satisfy. Everything else in this repository is a consequence.