Bitcoin Script as a Macro-Expanded Turing Framework

2025-06-11 · 18,101 words · Singular Grit Substack · View on Substack

A Compile-Time Loop Unrolling Architecture for Deterministic Contract Execution

Macro Compiler Option: --macro-unroll-loops

Enable compile-time loop expansion via macro definitions, transforming static higher-level constructs into deterministic, linear Bitcoin Script sequences.

This compiler flag enables support for macro-based loop constructs that are expanded into raw Bitcoin Script during compilation. It assumes the full symbolic expression of loops with known bounds and predefines all iteration steps without runtime decision-making. This allows simulation of iterative and recursive computation in a finite domain, converting high-level logic into verifiable low-level instructions suitable for blockchain deployment.Subscribe

All results in this paper hold under the implicit assumption that every script is finite and every stack bounded by the byte length of the containing transaction.

1. Introduction: Determinism and Symbolic Computation in Bitcoin Script

-

Definition and purpose of Bitcoin Script

-

The deterministic nature of Bitcoin contract evaluation

-

Constraints of Bitcoin Script (no unbounded loops, no recursion)

-

The emergence of symbolic expansion as a method for simulating advanced control flow

-

Thesis: Bitcoin Script, when viewed through the lens of compile-time macro expansion and 2PDA abstraction, is capable of simulating Turing-complete logic over finite state machines

Bitcoin Script, the contract language integral to Bitcoin's transaction validation system, is frequently described as intentionally non-Turing complete—a constrained, minimalistic stack-based language engineered to prevent unbounded computation. This characterization, while semantically accurate in operational terms, occludes a deeper computational structure that emerges under static expansion and symbolic encoding. The proposition advanced herein is that Bitcoin Script, when interpreted as a two-stack pushdown automaton (2PDA) and when used with compile-time loop unrolling via macro expansion, simulates the operational completeness of a Turing machine over bounded tapes. The result is a deterministic, linearised execution model—computationally rich yet statically verifiable—bridging the abstraction of symbolic computation with the formal semantics of execution in a distributed consensus protocol.

At its core, Bitcoin Script evaluates a series of opcodes through a deterministic interpreter. Each opcode is executed linearly, manipulating one of two primary data structures: the main stack and the alt stack, both behaving as last-in, first-out (LIFO) stores. These dual stacks permit the emulation of state machines with explicit transition rules and symbol-push semantics. Yet unlike traditional automata operating over unbounded input, Bitcoin Script enforces finiteness via fixed script length and prohibits operations such as runtime loops, recursion, or arbitrary memory access. These constraints are not limitations per se, but rather design features aimed at preserving the deterministic and bounded evaluation critical to Bitcoin’s consensus mechanism.

To simulate arbitrary computation within this regime, one must introduce the notion of symbolic expansion. Here, any iterative or recursive construct is unrolled at compile time, replacing loop bodies with statically repeated opcode sequences. This corresponds, in automata-theoretic terms, to the substitution of dynamic transition functions with enumerated transition paths over a bounded configuration space. Thus, rather than operating as a general-purpose Turing machine with dynamically computed control flow, Bitcoin Script becomes a symbolic reification of Turing computation: the entire transition system is materialised in the script itself, and the interpreter merely follows precomputed paths.

This approach is analogous to circuit-based computation, where logical gates are composed statically into deterministic circuits that simulate computation without the need for dynamic control logic. A well-known example is the use of Boolean circuits to simulate Turing machines in proof-of-work verifiability contexts. The unrolling of loops into opcode sequences in Bitcoin Script functions identically—translating temporal iteration into spatial repetition. Each iteration becomes an instantiation of the loop body with specific constants substituted, and control is encoded linearly rather than dynamically. In this respect, Bitcoin Script with unrolled macros behaves like a uniform family of circuits of polynomial size—each contract becomes a static representation of a computation bounded by the input length and the maximum stack depth.

The abstraction enabling this transformation is the macro preprocessor—an external symbolic interpreter responsible for analysing loop bounds, body semantics, and substitution parameters. When a macro such as LOOP(n, body_macro) is invoked, the compiler expands it into n contiguous segments, each corresponding to a single iteration of the loop. Crucially, each segment contains literal constants and concrete opcodes, eliminating the need for runtime variable evaluation or conditional control. What results is a flattened control structure—an explicit sequence of deterministic transitions executable without conditional branches or recursion. The complexity of control is absorbed entirely into the preprocessor, and the Bitcoin Script interpreter becomes a passive evaluator of pre-derived logic.

It is within this paradigm that the language achieves finite symbolic completeness. Given an input of length n, and a preprocessor that emits all n steps of an algorithm as opcode sequences, Bitcoin Script simulates any computation whose time and space complexity are functions of n. The only restriction is that the expansion must terminate at compile time, and the resulting script must respect the operational constraints of the Bitcoin protocol (e.g. maximum script size, operation count limits). This form of symbolic completeness does not contradict the language’s foundational restrictions, but instead recontextualises them as guardrails within a compile-time formal system.

Mathematically, this can be formalised as follows. Let T be a Turing machine with a bounded input alphabet Σ and a fixed tape length k. Let δ: Q × Σ → Q × Σ × {L, R} be its transition function. One can construct a macro-expanding compiler M that maps T's transition system into a set of Bitcoin Script macro definitions μ: ℕ → ScriptSegments such that for each transition δ(q, σ) = (q′, σ′, D), μ emits a sequence of stack manipulations simulating symbol overwrite, state transition, and head movement encoded as push, pop, and alt-stack transfers. The unrolled script S = ⋃_{i=0}^{k-1} μ(i) then emulates the full computation path of T over its bounded tape. The interpreter's role reduces to evaluating S linearly—there is no ambiguity, nondeterminism, or control flow: the computation is realised in the structure of the script itself.

In this light, Bitcoin Script with macro expansion becomes a symbolic automaton compiler—a device that transforms high-level logic into verifiable, deterministic opcode streams. It does not compute in the traditional dynamic sense; instead, it reifies computation, encoding its logical trajectory as a syntactic artefact. This design yields a system of profound formal elegance: a language simultaneously restricted and expressive, minimalist yet complete within its domain, whose power lies not in runtime flexibility but in symbolic compile-time generality. Through --macro-unroll-loops, we uncover this latent expressiveness, enabling Bitcoin Script to serve not just as a verification tool, but as a medium for representing bounded computation in a secure, auditable form.

This essay proceeds to establish the formal foundations of this model, define the semantics of macro expansion, and demonstrate its capacity to simulate complex control logic within a bounded deterministic framework. The structure follows a progressive elaboration: first framing the 2PDA correspondence, then formalising the macro compilation logic, and finally proving completeness and illustrating practical applications. Through this, we present Bitcoin Script not merely as a restricted language, but as a symbolic substrate for real-world, verifiable computation.

It is not Bitcoin Script that fails to be Turing-complete—it is the myth of infinite computation that fails. All real systems halt. All systems run in finite space and time. Bitcoin Script, when restored to its full arithmetic and stack semantics, reflects this constraint honestly. It is complete in the only way any language can ever be: symbolically exhaustive, bounded only by the patience of its author and the block limits they can afford. To deny its Turing power is not humility, but misdirection.Closure Over OPCODE-SIZED Inputs: The document presents an unlimited instruction size except for block-size boundaries. While true in principle, miner policy may limit maximum data-push size or disallow certain non-standard sequences even within Script-valid ranges. These implementation-layer details may affect real-world deployment.

2 Theoretical Foundations: Bitcoin Script as a Two-Stack Pushdown Automaton and Its Turing-Equivalent Consequences

-

Formal definition of PDA (Pushdown Automaton) and 2PDA

-

Proofs and references demonstrating Turing-completeness of 2PDA

-

Mapping Bitcoin Script components: main stack ↔ Stack1, alt stack ↔ Stack2

-

Constraint: input size and control logic must be finite and unrolled at compile time

-

Consequence: Bitcoin Script as a finite-state expansion engine

Bitcoin Script is often caricatured as a narrowly constrained validation language, yet that verdict reflects the post-2010 truncation imposed by BTC rather than the capabilities of Satoshi’s original implementation, preserved today in Bitcoin SV (BSV). The first release included the full arithmetic instruction set – OP_MUL, OP_MOD, bit-shift operators, and more – and allowed scripts to grow until they met the physical limits of a block. BTC later introduced a hard ceiling of 10 000 bytes per script, an opcode-count cap of 201, and disabled several key operations; none of these restrictions exists in BSV. Within this unconstrained milieu, macro expansion of loops realises its full theoretical power.

2·1 Pushdown Automata Revisited

A deterministic pushdown automaton (PDA) is the septuple (Q, Σ, Γ, δ, q₀, Z₀, F):-

Q – finite set of control states

-

Σ – input alphabet

-

Γ – stack alphabet

-

δ – transition relation Q × (Σ ∪ {ε}) × Γ → Q × Γ*

-

q₀ – start state ∈ Q

-

Z₀ – initial stack symbol ∈ Γ

-

F – accepting states ⊆ Q

With a single LIFO store, a PDA recognises exactly the context-free languages. The limitation arises because information pushed deep in the stack becomes inaccessible without destructive pops, so simultaneous unbounded left- and right-context cannot be preserved.

2·2 Deterministic Two-Stack Automata and Turing Equivalence

Adding an independent second stack elevates the machine to full Turing power; classical results by Ginsburg–Greibach (and echoed in Hopcroft–Ullman) show that a deterministic two-stack PDA (2PDA) can simulate an arbitrary Turing machine T. Split the Turing tape at the head: push the left half in reverse onto Stack 1, the right half in forward order onto Stack 2. A left move pops from Stack 1 and pushes to Stack 2; a right move performs the dual; symbol rewrites are handled at the active top. Because each stack is individually unbounded, together they emulate an infinite bidirectional tape. Hence the language classes of 2PDA and Turing machines coincide.

2·3 Mapping Bitcoin Script to the 2PDA Model

Bitcoin Script provides exactly two stacks:-

Main stack 𝕊 – operands for all arithmetic and logical operations

-

Alt stack 𝔄 – auxiliary LIFO store reached by OP_TOALTSTACK and OP_FROMALTSTACK

During evaluation the interpreter configuration ⟨pc, 𝕊, 𝔄⟩ advances linearly through the byte-encoded script Π that resides in the spending transaction. Because Script is pure and side-effect-free there is a total deterministic function

Eval_BSV : ℕ × Stack × Stack → ℕ × Stack × Stack

that consumes one opcode per step. With no BTC-style caps, the only bound on |Π| is the block size σ ≤ 4 GiB. Thus the triple (𝕊, 𝔄, Π) realises a genuine deterministic 2PDA: the two stacks supply unbounded memory and the interpreter’s dispatch table is the finite-state control.

2·4 Macro Expansion as Compile-Time Control Encoding

Bitcoin Script lacks OP_JUMP and any runtime branch instruction, yet a macro pre-processor can unroll arbitrary bounded loops into a flat instruction sequence. For a loop macro

Loop(n, i ↦ B(i)) = B(0) ‖ B(1) ‖ … ‖ B(n − 1)

the compiler emits n concrete copies of the body B, each specialised at its index. Temporal iteration becomes spatial duplication, just as a Turing machine running t(n) steps can be mapped to a Boolean-circuit family of size proportional to t(n) log t(n). The Bitcoin node, oblivious to the high-level intent, merely executes a pre-arranged chain of state transitions.

2·5 Resource Bounds: Original Bitcoin vs BTC

In the authentic protocol lineage (BSV) the only hard limits stem from the block-size parameter and the validator’s hardware; there is no intrinsic script-size ceiling, no opcode-count cap, and every original arithmetic opcode remains live. BTC’s restrictions – 201-operation limit, 10 kB script size, disabled OP_MUL, OP_LSHIFT, and others – were introduced between 2010 and 2013 as restriction patches. They amputated the expressive arm of the 2PDA by bounding |Π| and removing essential transition primitives. Hence macro expansion survives in BTC only for toy-sized loops, whereas in BSV it can cover the full breadth of any contract the miner is willing to publish.

2·6 Finite-Space Turing Completeness of Macro-Expanded Scripts

Let T_k be a Turing machine confined to k tape cells, and let t(k) be a step bound chosen by the contract author. A macro compiler ℳ encodes the exact trace of T_k for t(k) steps into a script Π_k of length Θ(t(k)). Because Π_k is finite, the 2PDA evaluator halts with the same final configuration that T_k would reach. The mapping ℳ⟨T_k, t(k)⟩ → Π_k is computable and, under BSV rules, unconstrained in principle. The pair (BSV-Script, ℳ) therefore attains finite-space Turing completeness: every bounded Turing computation has a homomorphic image in the language. Completeness is achieved symbolically; all bookkeeping shifts from runtime to compile time, where it can be audited, hashed, and immutably recorded on-chain.

Because execution is deterministic and stateless outside the two stacks, verification reduces to confirming that Π_k respects Script syntax and that its post-conditions – expressed as stack predicates – hold at the final program counter. This static-correctness paradigm is the foundation for high-assurance smart contracts in the original protocol design. Bitcoin Script, under macro expansion, is not a minimalist curiosity but a vessel for rigorous, Turing-robust computation within the provably finite confines required by global consensus.


3. Compiler Directives: Defining --macro-unroll-loops

-

Specification of the compiler flag

-

Semantic interpretation: replace for/while constructs with flat opcode sequences

-

Compilation model: tokenising loop body macros; repeated expansion

-

Determinism guarantee: each script segment is verifiable and bounded

-

Limitations: growth in script size; maximum block script size

The moment a contract author chooses to write loops in a higher-level Bitcoin-Script-superset language, the burden of iteration must shift irrevocably from run-time to compile-time. The flag --macro-unroll-loops is the mechanism that forces that shift. It instructs the compiler to expand every bounded loop, every parametric recursion, and every symbolic range into a concrete, linear instruction sequence before a single byte reaches the blockchain. This section defines the directive formally, describes the expansion algorithm, proves its soundness, and details the static-analysis steps that guarantee the resulting script remains within the deterministic confines of the protocol lineage maintained by BSV.

3·1 Compiler Pipeline in Context

Source (𝓛ₘ) → Lexer → Token stream → Macro Expander ⇢ Flattened token stream → Parser → AST → Static analyser → Code Emitter → Bitcoin Script-

The macro expander operates before parsing so that parsing is performed on a loop-free language.

-

The presence of --macro-unroll-loops is mandatory for any source file that contains the keywords for, while, repeat, or a user-defined macro tagged as iterative. If the directive is missing and a loop construct is encountered, the compiler aborts.

3·2 Macro Grammar

Two production rules extend the baseline grammar:

• macro name(param₁, …, paramₙ) { … } — definition, captured in the environment ℰ.

• for i = a to b { … } or for i in range(a, b) { … } — bounded iteration site.

Parameters are evaluated at compile-time; free identifiers are resolved in the lexical environment dominant at the call site.

3·3 Name Binding and Hygiene

The macro system is α-hygienic:

-

Every parameter is substituted using unique De Bruijn indices, guaranteeing no accidental capture.

-

Shadowing is disallowed across macro boundaries; re-declaration triggers a compile-time error.

-

A macro may reference another macro but recursion depth must be statically finite; cyclical references without a decreasing bound are rejected.

3·4 Expansion Algorithm Α

Let the loop be

for i = 0 to n-1 { B(i) }

where B(i) is the loop body parameterised by the iteration index.-

Bound resolution Evaluate n. It must reduce to a non-negative integer n ∈ ℕ.

-

Iteration For k = 0 … n-1 produce a specialised copy Bₖ = B(i ← k).

-

Concatenation Π = B₀ ◦ B₁ ◦ … ◦ Bₙ₋₁.

-

Tail-call collapse Contiguous pushes of literal data are merged into a single PUSHDATA op when profitable, reducing byte count without changing semantics.

Time complexity Θ(n · |B|)  Space complexity Θ(n · |B|).

Because BSV imposes no intrinsic script or opcode caps, n is bounded only by miner policy and physical block size.

3·5 Symbolic Stack-Effect Analysis

Each opcode op has a statically known stack delta σ(op) ∈ ℤ.

Define cumulative effect Σ(k) = Σ₀ + ∑_{j = 0}^{k} σ(Πⱼ). The analyser checks

• Σ(k) ≥ 0 for every 0 ≤ k < |Π| (no under-flow).

• Σ(|Π|) equals the expected post-condition (usually 1 for a Boolean result or 0 for OP_RETURN branches).

Failure to satisfy either predicate halts compilation.

3·6 Opcode Legality and Version Table

A version table records the availability of every opcode per consensus fork:

Opcode Status BSV Status BTC Note
——— ——— ——— ———
OP_MUL live disabled arithmetic
OP_LSHIFT live disabled bitwise
OP_VERIFY live live terminator

The expander queries this table. If the target network is BTC and the body requires a disabled opcode, the compiler emits diagnostics and suggests pre-computing the constant or shifting to BSV.

3·7 Soundness Theorem

Theorem (Soundness of --macro-unroll-loops).

Let S be well-formed source in 𝓛ₘ and let Π = Α(S) be the flattened script produced under the directive. Then-

Π is syntactically valid Bitcoin Script under the chosen consensus ruleset.

-

The evaluation relation ⟨0, 𝕊₀, 𝔄₀⟩ ⇢* ⟨pc_f, 𝕊_f, 𝔄_f⟩ terminates with pc_f = |Π|.

-

All static assertions declared in S (stack height, invariant predicates, range guards) hold in ⟨𝕊_f, 𝔄_f⟩.

Proof sketch – Item 1 by construction and opcode-table filtering; Item 2 because Π is finite and branch-free; Item 3 by induction on concatenation of bodies: if each Bₖ preserves its local post-condition, their serial composition preserves the global predicate.

3·8 Verification Phases

-

Syntax check → explicit opcode table.

-

Stack-effect pass → linear scan.

-

Gas-like fee estimation → sum of per-opcode cost; warns if miner policy may refuse.

-

Predicate solver → SMT pass over integer variables produced by symbolic execution; proofs are embedded as comments for downstream auditors.

Every phase runs in linear or amortised-linear time in |Π|.

3·9 Interaction with Miner Policy and Block Limits

Although the protocol sets no hard ceiling, miners advertise soft limits X_bytes and Y_ops. The compiler emits a header comment indicating:

• Script length in bytes.

• Opcode count.

• Estimated execution cost.

If any dimension exceeds the default policy windows published by major BSV pools, the toolchain flags the contract as potentially non-standard and provides a list of pools whose custom policies are lenient enough to include the transaction.

3·10 Hard-Fork Invariance and Forward Compatibility

Because the directive expands control flow into timeless flat code, future forks that add opcodes or raise limits do not invalidate existing contracts; the script already contains its full semantics. Only a fork that removes an opcode used in Π could break execution. To mitigate that possibility the compiler stores, inside the script’s unlocking field, a hash of the opcode-version table used at compile time, enabling automated forward-compatibility scanners to detect incompatibilities in advance of activation.

3·11 Summary

The --macro-unroll-loops directive transforms loop constructs into deterministic sequences, replaces temporal complexity with spatial redundancy, and lifts the contract into a form that a Bitcoin node can evaluate with zero ambiguity. The section above has specified its grammar, expansion algorithm, static-analysis guarantees, and interaction with consensus and miner policy. Armed with this foundation, contract authors gain a rigorous, auditable pathway from high-level iterative intent to fully verifiable on-chain logic.


4. Macro Syntax: Declarative Loop Definitions in High-Level Assembly

-

General syntax for macro declaration

-

LOOP(n, body_macro) – declarative logic

-

Inline constant expansion and parameter substitution

-

Example: square_macro(i) expands to OP_i OP_DUP OP_MUL

-

Use of symbolic constants, e.g. $i, $n, $result

In order to leverage the --macro-unroll-loops directive, contract authors must be able to express iteration, repetition, and indexed logic within a compact and semantically rich syntax that can be deterministically expanded by the compiler. This section formalises the syntax of such macro definitions, the binding structure for loop parameters, and the translation rules that convert declarative loop constructs into a flat stream of Bitcoin Script opcodes. Importantly, these macros must be purely symbolic, hygienic, and statically resolvable. They are not meta-programming conveniences—they are the core interface by which high-level logic is made concrete in the finite substrate of Bitcoin Script.

4·1 Canonical Loop Form and Semantics

At the highest level, a declarative macro takes the form:

LOOP(n, body_macro)

where:-

n is a natural number known at compile-time,

-

body_macro is a lambda function or inline macro that returns a list of Bitcoin Script instructions parameterised by the loop index i.

The semantics are:

LOOP(n, B(i)) ⇝ B(0) ‖ B(1) ‖ … ‖ B(n−1)

This is the canonical unrolling operation. The result is a sequence of n distinct script fragments, each specialised for its index. Control flow is thus translated into literal repetition; parameterised logic becomes concrete opcode structure.

Example (simple iteration):

LOOP(3, i ↦ [OP_PUSH(i)])

expands into:

OP_0

OP_1

OP_2

This represents a for-loop from i = 0 to 2, pushing each integer onto the stack. Note that OP_0 to OP_16 are Bitcoin Script shortcuts for small integers. Larger constants are emitted as PUSHDATA segments.

4·2 Inline Macro Functions

In practice, macro bodies may be defined inline or pre-bound:

def square_macro(i):

return [OP_PUSH(i), OP_DUP, OP_MUL]

LOOP(4, square_macro)

expands to:

OP_0 OP_DUP OP_MUL

OP_1 OP_DUP OP_MUL

OP_2 OP_DUP OP_MUL

OP_3 OP_DUP OP_MUL

Each expansion of the body is fully specialised: constants are embedded directly; control variables are removed. There is no trace of symbolic reference at runtime.

If OP_MUL is not permitted under the target ruleset, the macro body can be altered to:

def square_macro(i):

return [OP_PUSH(i * i)]

with the same loop. The precomputation occurs in the macro, not the script.

4·3 Declarative Binding Environment

The loop index i is a bound variable. It must be:

• Explicitly bound per invocation;

• Lexically scoped within the macro body;

• Shadow-free (redeclaration not permitted);

• Substituted statically at macro-expansion time.

In a loop-nesting context:

LOOP(2, i ↦ LOOP(3, j ↦ [OP_PUSH(i), OP_PUSH(j)]))

expands to:

OP_0 OP_0

OP_0 OP_1

OP_0 OP_2

OP_1 OP_0

OP_1 OP_1

OP_1 OP_2

There is no leakage of i or j across macro boundaries. Substitution is α-hygienic.

4·4 Macro Closure and Purity Constraints

A macro used in --macro-unroll-loops mode must be pure. That is:-

It must produce a list of opcodes, not dynamic data.

-

It must not reference external variables that are not declared as parameters.

-

It must not perform non-deterministic evaluation (e.g., random(), get_time()).

This guarantees that macro expansion is a total function of its inputs and does not depend on runtime state.

Allowed:

def mul_macro(i, j):

return [OP_PUSH(i), OP_PUSH(j), OP_MUL]

Disallowed:

def impure_macro(i):

return [OP_PUSH(i), OP_PUSH(random())] # forbidden: random not deterministic

If a macro fails purity constraints, compilation fails with a deterministic error message including the unresolved symbol.

4·5 Control Flow Macros and Conditional Expansion

Static conditional macros are also supported:

def parity_macro(i):

if i % 2 == 0:

return [OP_PUSH(i), OP_PUSH("even")]

else:

return [OP_PUSH(i), OP_PUSH("odd")]

Then:

LOOP(3, parity_macro)

yields:

OP_0 "even"

OP_1 "odd"

OP_2 "even"

Dynamic branching is not used; all decisions occur at compile time. This enforces Script's determinism and enables full verifiability.

4·6 Literal Concatenation and Opcode Optimisation

The compiler applies post-expansion optimisations:

• Adjacent OP_PUSH(x) followed by OP_TOALTSTACK becomes a single OP_PUSH_TOALT(x) if permitted.

• Repeated sequences are identified and optionally replaced with macro-hints for manual optimisation.

• If a loop body is static and identical across iterations, the compiler emits a diagnostic: "Consider using manual repetition; macro variable unused."

These hints are advisory; they do not affect script correctness but allow contract authors to reduce byte size when desired.

4·7 Symbol Binding Tables and Debug Mapping

For auditing purposes, the compiler records:

• Each macro invocation site, with parameter values.

• The index of each expanded block in the final script.

• A reverse mapping from byte offset to macro source line.

This enables symbolic debugging of macro-generated scripts and supports formal audits, step-by-step evaluation tracing, and test harness generation.

4·8 Future-Proof Syntax Extension

The macro system is extensible. Planned additions include:

• MAP(list, lambda) — macro expansion over list literals.

• ZIP(list₁, list₂, lambda) — element-wise pairing.

• IF(cond, then_macro, else_macro) — fully static conditionals.

None of these compromise determinism. All expansions must resolve to flat, linear sequences of known opcodes.

4·9 Summary

Macro syntax in the context of --macro-unroll-loops is not syntactic sugar. It is a formal apparatus for translating bounded iteration, parametrised computation, and symbolic contracts into literal sequences of Bitcoin Script instructions. The loop index becomes a compile-time constant. The macro body becomes a concrete sequence of state transformations. And the final script is a deterministic artefact—transparent, auditable, and ready for on-chain verification. Through this syntax, authors encode complexity without ambiguity, logic without control flow, and intention without interpretation.


5. Examples of Loop Expansion: Arithmetic and Control Patterns

-

LOOP_INC(n) — pushing incremental values to the stack

-

SQUARE_LOOP(n) — duplicating and squaring integers

-

ADD_CHAIN(n) — iterative accumulation of values

-

Multi-stack interaction via OP_TOALTSTACK / OP_FROMALTSTACK

-

Conditional logic via precomputed branches (OP_IF, OP_ELSE, OP_ENDIF), replicated per iteration

Having established the formal structure and syntactic framework for macro-based unrolling, we now illustrate the practical application of --macro-unroll-loops through detailed examples. These cover not merely the transformation of syntactic constructs into Bitcoin Script, but their underlying computational intent: the simulation of arithmetic, the encoding of control structures, and the design of contract logic as a sequence of statically expanded state transitions. Each example demonstrates the shift from high-level declarative logic to low-level opcode emission—where iteration is expressed spatially, not temporally, and every control decision is pre-resolved at compile time.

5·1 Increment Loop: Linear Push of Indexed Constants

The most basic form of loop is the incrementation of an integer index and the pushing of each value onto the stack. Consider:

LOOP(3, i ↦ [OP_PUSH(i)])

This expands to:

OP_0

OP_1

OP_2

Each line pushes a literal constant onto the main stack. The result is a stack state [0, 1, 2] (with 2 at the top). This corresponds to the loop:

for (int i = 0; i < 3; i++) stack.push(i);

The simplicity of this example belies its importance: this construct forms the base case for indexed iteration, and every higher-order pattern builds on it.

5·2 Square Each Index: Arithmetic Loop Body

To compute squares of a known range of integers, we may define:

def square_macro(i):

return [OP_PUSH(i), OP_DUP, OP_MUL]

Invoked as:

LOOP(3, square_macro)

This emits:

OP_0 OP_DUP OP_MUL

OP_1 OP_DUP OP_MUL

OP_2 OP_DUP OP_MUL

The stack ends as [0, 1, 4] (with 4 on top). This loop squares each index by pushing i, duplicating it, and applying OP_MUL. Note: OP_MUL is available only in BSV, not BTC. In a BTC-targeted build, one must instead precompute and push the literal square:

def square_macro(i):

return [OP_PUSH(i * i)]

yielding:

OP_0

OP_1

OP_4

This substitution preserves logic while ensuring compatibility. The loop body is symbolic; the emitted script is purely concrete.

5·3 Conditional Expansion: Tagging Even/Odd Values

Control logic can be applied statically within the macro body. For instance:

def parity_macro(i):

if i % 2 == 0:

return [OP_PUSH(i), OP_PUSH("even")]

else:

return [OP_PUSH(i), OP_PUSH("odd")]

Then:

LOOP(4, parity_macro)

produces:

OP_0 "even"

OP_1 "odd"

OP_2 "even"

OP_3 "odd"

No branching occurs at runtime. The conditional is resolved during macro expansion. The output is a static decision tree, flattened into a linear opcode stream. This ensures complete transparency and total auditability.

5·4 Stack-Based State: Simulating an Accumulator

Suppose we wish to emulate cumulative addition: for each i from 0 to n−1, add i to the top of the stack. With initial stack state [0], we define:

def add_macro(i):

return [OP_PUSH(i), OP_ADD]

and expand:

OP_0

OP_PUSH(1) OP_ADD

OP_PUSH(2) OP_ADD

OP_PUSH(3) OP_ADD

The resulting stack is [6], having performed ((0 + 1) + 2) + 3. This simulates a running accumulator.

We could similarly build product accumulation (OP_MUL) or emulate min/max using conditional selection—each statically unrolled into deterministic transitions.

5·5 Multi-Stack Manipulation: Alternating Between Stacks

To demonstrate use of both stacks, consider a loop that transfers values from main to alt, then back again:

def to_alt_macro(i):

return [OP_PUSH(i), OP_TOALTSTACK]

def from_alt_macro(_):

return [OP_FROMALTSTACK]

Then:

LOOP(3, to_alt_macro)

LOOP(3, from_alt_macro)

yields:

OP_0 OP_TOALTSTACK

OP_1 OP_TOALTSTACK

OP_2 OP_TOALTSTACK

OP_FROMALTSTACK

OP_FROMALTSTACK

OP_FROMALTSTACK

This script reverses the order of elements pushed, as the alt stack operates LIFO. Final main stack: [2, 1, 0]. This is critical for stack reordering, reversing, or queue simulation.

5·6 Nested Loops: Cartesian Product Expansion

A nested loop encodes pairwise computation over index ranges:

LOOP(2, i ↦ LOOP(2, j ↦ [OP_PUSH(i), OP_PUSH(j)]))

expands to:

OP_0 OP_0

OP_0 OP_1

OP_1 OP_0

OP_1 OP_1

Each inner iteration emits a pair (i, j), yielding the full Cartesian product of [0,1] × [0,1]. This enables simulation of two-dimensional structures—matrix population, cell-wise logic, or constraint grids—within a statically flattened execution trace.

5·7 Switch-Like Macros: Static Selection Structures

A switch statement may be simulated using conditional macro logic:

def case_macro(i):

if i == 0:

return [OP_PUSH("zero")]

elif i == 1:

return [OP_PUSH("one")]

else:

return [OP_PUSH("default")]

Expanded as:

LOOP(3, case_macro)

yields:

"zero"

"one"

"default"

Note that each condition is resolved statically. If the macro is used to process inputs (e.g. OP_PICK or OP_EQUAL comparisons), each case’s consequences can be unrolled, allowing branch-free substitution of match logic.

5·8 Byte-Level Arithmetic: Simulating Bitwise Iteration

In the absence of dynamic loops, bitwise operations must be unfolded explicitly. For example, to construct a loop that shifts a value and accumulates:

def shift_macro(i):

return [OP_DUP, OP_PUSH(i), OP_LSHIFT]

Given a value on the stack, this macro duplicates it and shifts it left by i bits. The loop:

LOOP(4, shift_macro)

unrolls:

OP_DUP OP_0 OP_LSHIFT

OP_DUP OP_1 OP_LSHIFT

OP_DUP OP_2 OP_LSHIFT

OP_DUP OP_3 OP_LSHIFT

The stack now contains four shifted versions of the same value: x, x≪1, x≪2, x≪3. This can be extended into more complex patterns of binary masks, AND-chains, or base conversions—fully within static scope.

5·9 Summary

These examples illustrate the essential paradigm: every dynamic construct has a static analogue when bounds are known. Loops become concatenations. Conditionals become mutually exclusive code blocks. Recursion becomes repetition. The Bitcoin Script interpreter no longer interprets logic—it executes outcomes. Through macro unrolling, the structure of the computation is externalised, auditable, and fixed. This is not a language for expressing intent; it is a mechanism for materialising it.

With each example, we move closer to a design discipline in which the entire execution path of a contract is deterministically visible before the transaction is mined. This not only enhances verifiability—it redefines what it means to write a program for the blockchain. The next section extends this discipline further: modelling Turing machine simulations directly in macro-expanded form.


6. Handling Missing Opcodes: Arithmetic Without OP_MUL

-

Historic disabling of OP_MUL in BTC; restored in BSV

-

Emulation techniques: repeated addition; shift-and-add methods

-

Static precomputation fallback: PUSHDATA of squared constants

-

Ensuring compatibility across forks and protocol variants

The expressive power of macro expansion in Bitcoin Script is naturally bounded by the available opcode set. In the original implementation (retained in Bitcoin SV), arithmetic operations such as OP_MUL, OP_DIV, OP_MOD, and bitwise manipulation opcodes such as OP_LSHIFT, OP_RSHIFT, and OP_AND were present and operational. These were deliberately disabled in BTC under the assumption that restricting the arithmetic layer would prevent resource abuse and remove the threat of non-terminating logic. As a result, authors targeting BTC must emulate arithmetic that is native in BSV. This section provides precise techniques for handling such absence using static symbolic computation and macro-based substitution.

6·1 Understanding the Absence: Protocol-Level Deactivation

Under BTC rules, OP_MUL, OP_DIV, OP_MOD, and several bitwise opcodes (OP_LSHIFT, OP_RSHIFT, OP_AND, etc.) are disabled and generate script failure if executed. They are present in the opcode table but marked as invalid. Consequently, any attempt to include them—even through macro expansion—must be blocked at compile time. This restriction is enforced per target in the opcode legality table, and if a disallowed opcode is encountered, a compile-time error is issued with recommended substitution strategies.

In BSV, none of these restrictions apply. All original opcodes are live, so direct arithmetic is preferred and fully valid. The compilation target determines strategy.

6·2 Symbolic Emulation by Precomputed Constant Folding

If OP_MUL is not available, the most direct substitution is to eliminate dynamic computation and statically emit the result of any arithmetic operation. For example:

def square_macro(i):

return [OP_PUSH(i * i)]

Expanded:

OP_PUSH(0)

OP_PUSH(1)

OP_PUSH(4)

OP_PUSH(9)

This strategy, known as constant folding, performs arithmetic at compile time rather than expressing it as part of the script. It preserves semantic intent and ensures compatibility even under strict opcode limitations.

This approach generalises. Consider:

def triple_sum_macro(i):

return [OP_PUSH(i + i + i)]

becomes:

OP_0

OP_3

OP_6

OP_9

...

The macro compiler serves as the arithmetic engine, and Script becomes a passive conveyor of constants.

6·3 Manual Simulation via Static Instruction Sequences

When symbolic computation is not possible—e.g. when the operands are dynamic but bounded—a limited form of operation simulation can be achieved via macro-expanded instruction chains.

6·3·1 Multiplication by Repetition

To simulate a × b where b is a known constant and a is a value on the stack:

def mul_by_const_macro(b):

def body(_):

return [OP_DUP] + [OP_ADD] * (b - 1)

return body

Example (b = 4):

OP_DUP

OP_ADD

OP_ADD

OP_ADD

This duplicates the value and adds it to itself three times, yielding 4 × a. This simulates OP_MUL for small constants. It is linear in b.

6·3·2 Bitwise Left Shift as Multiplication by Powers of Two

If OP_LSHIFT is unavailable, simulate a << k as a × 2ᵏ:

def lshift_macro(k):

return [OP_PUSH(1 << k), OP_MUL]

If OP_MUL is also unavailable, further expand:

def lshift_emulated_macro(k):

def body(_):

return [OP_DUP] + [OP_ADD] * ((1 << k) - 1)

return body

Each macro expansion statically encodes the shift magnitude as repetition.

6·4 Pattern Rewriting for Control Logic Substitution

In the absence of arithmetic-based branching, control logic must also be unrolled statically. Consider simulating:

if (x == 2) { do A } else { do B }

BTC disallows OP_EQUALVERIFY in some contexts if followed by restricted logic. Instead of dynamically branching, structure the macro to emit:

OP_PUSH(2)

OP_EQUAL

OP_IF

… A logic …

OP_ELSE

… B logic …

OP_ENDIF

If Script branching is unsafe due to limits on stack depth or verification policy, emit both branches statically under pre-evaluated control:

def logic_macro(i):

if i == 2:

return [... A ...]

else:

return [... B ...]

Then:

LOOP(4, logic_macro)

Generates the fixed path of computation—no OP_IF, no runtime branching.

6·5 Partial Emulation of Bitwise Operations Using Shifts and Masks

Assume OP_AND is disabled (BTC). To test if a particular bit is set in a known position:-

Shift right until the target bit is at position 0,

-

Then check parity using modulo.

def bit_test_macro(position):

return [OP_PUSH(position), OP_RSHIFT, OP_PUSH(1), OP_AND]

If OP_RSHIFT and OP_AND are also unavailable:

def emulated_bit_test(position):

shift = [OP_DUP] + [OP_DIV2] * position # OP_DIV2 must be macro-defined

return shift + [OP_PUSH(2), OP_MOD]

Here, OP_DIV2 is itself a macro:

def OP_DIV2():

return [OP_PUSH(2), OP_DIV]

Again, if division is disallowed, one must precompute the mask and apply repeated subtraction with conditional logic—this may bloat script size rapidly.

6·6 Summary of Substitution Hierarchy

To deal with missing opcodes:-

Prefer precomputation. If operands are known, emit constants.

-

Use static loops. Simulate multiplication via repeated addition.

-

Avoid branching. Replace conditions with pre-expanded selections.

-

Rewrite patterns. Model logic with available primitives.

-

Abort gracefully. If the desired logic cannot be expressed under the current opcode set, halt with diagnostic information.

The macro compiler is a symbolic interpreter. It replaces runtime complexity with static expansion, provided all control paths are bounded and all logic is expressible through the permitted opcodes. The more capable the opcode set, the smaller the script. The more limited the environment, the larger and more repetitive the emitted logic.

In BSV, no such compromises are necessary. All original opcodes are live. Arithmetic and bitwise computation may be expressed directly. The macro system becomes an optimisation convenience, not a workaround. But when targeting constrained rule sets like BTC, the macro expander becomes a survival tool—converting every disabled operation into a symbolic circuit, unrolled into space. This ensures that no matter the consensus context, deterministic computation remains possible, provable, and expressible within the Bitcoin Script domain.


7. Execution Semantics: Stack Trace and Evaluated Form

-

Simulated stack transitions per macro line

-

Verifiability by script interpreter

-

Reverse engineering final stack state for auditing

-

Deterministic audit paths for each unrolled macro branch

Once a script has been generated via macro expansion—its loops unrolled, its conditionals resolved, and its arithmetic either compiled or emulated—the question of how it runs becomes one not of algorithmic analysis, but of formal traceability. This section defines the evaluation semantics of a macro-expanded Bitcoin Script under the original BSV rule set, examines the shape and structure of its stack transitions, and characterises the final script as a deterministic, bounded transformation from one stack state to another. Every execution path is linear; every state transition is known at compile time. Execution becomes audit.

7·1 Initial Conditions and Interpreter State

The Bitcoin Script execution engine operates as a deterministic state machine. At each step, it consumes one opcode from the program stream Π and transforms a pair of LIFO stacks:-

Main stack: 𝕊

-

Alt stack: 𝔄

The interpreter state is the tuple ⟨pc, 𝕊, 𝔄⟩, where pc is the program counter: a zero-based index into Π. At each step, one opcode Π[pc] is executed, and the resulting tuple becomes ⟨pc + 1, 𝕊′, 𝔄′⟩. Because macro expansion eliminates all control flow—there are no OP_JUMP, OP_IF, or OP_ELSE—execution proceeds in a strict left-to-right walk over Π. This implies:-

pc is monotonic, pc₀ = 0, pcₙ = |Π|

-

No cycles, no branches, no conditions

-

All opcode effects are fixed, parameterless, and deterministic

This distinguishes macro-expanded Bitcoin Script from any traditional runtime language. Its semantics are replay semantics: the script is a witness of what has already been computed.

7·2 Transition Function and State Evolution

Let Eval₁ be the single-step interpreter function:

Eval₁(⟨pc, 𝕊, 𝔄⟩) ⇒ ⟨pc + 1, 𝕊′, 𝔄′⟩

The full evaluation over the script Π is defined by repeated application:

Evalₖ(⟨0, 𝕊₀, 𝔄₀⟩) ⇒ ⟨|Π|, 𝕊_f, 𝔄_f⟩

This is guaranteed to terminate, since:-

Π is finite (macro-expanded and fully linearised)

-

No branching or conditional recursion exists

-

Each opcode has a fixed, bounded cost and deterministic outcome

The interpreter is an accumulator of stack state. Each step mutates 𝕊 and 𝔄 according to the definition of the opcode. For example:-

OP_DUP: if 𝕊 = [x, …], then 𝕊′ = [x, x, …]

-

OP_ADD: if 𝕊 = [a, b, …], then 𝕊′ = [a + b, …]

-

OP_TOALTSTACK: moves the top of 𝕊 to 𝔄

Because the macro compiler can statically simulate each step, the final state ⟨𝕊_f, 𝔄_f⟩ can be predicted in advance.

7·3 Stack Trace: Auditing and Debugging

The most powerful feature of macro-expanded scripts is that every intermediate stack state can be computed statically. Given:-

The opcode sequence Π = [op₀, op₁, ..., opₙ₋₁]

-

The initial stack state 𝕊₀, 𝔄₀

It is possible to generate the stack trace: a full list of intermediate states:

⟨0, 𝕊₀, 𝔄₀⟩

⟨1, 𝕊₁, 𝔄₁⟩

⟨2, 𝕊₂, 𝔄₂⟩

⟨|Π|, 𝕊_f, 𝔄_f⟩

Each state follows directly from the previous. For a given contract, the compiler may emit this trace for verification. Stack traces are indispensable for:-

Formal audits

-

Assertion testing

-

Witness generation

-

Visualisation of contract logic

In the same way that a proof assistant may output a proof tree, the Bitcoin macro expander can emit a state tree, except that the tree is linear: a spine of transitions from source to sink.

7·4 Final Stack: Observable Behaviour and Script Truth

The terminal configuration ⟨|Π|, 𝕊_f, 𝔄_f⟩ determines the validity of the script. Under standard spending semantics:-

The script is considered valid if the top element of 𝕊_f is truthy (non-zero and non-null)

-

If OP_VERIFY is present, it consumes the top value and throws if falsy

-

If the stack is empty, execution fails

Thus, the script must be designed to leave one and only one truthy element on the stack unless explicitly cleared by OP_RETURN. The macro compiler ensures this by performing:-

Static stack effect analysis

-

Final stack shape validation

-

Assertion of terminal predicate (e.g. stack height = 1, top = true)

This guarantees that the script's conclusion is not merely syntactically valid, but semantically sound.

7·5 Symbolic Execution and Formal Verification

Because every opcode has a known symbolic effect, the macro compiler can emulate the script's behaviour without executing it. This allows:-

Substitution of symbolic variables

-

Formal proofs of contract invariants

-

Stack-type preservation

-

Automated checks of branch absence and loop flattening

This simulation—known as symbolic execution—is deterministic, finite, and bounded by |Π|. It transforms the script from an opaque sequence of bytes into a fully resolved trace of state transitions. The script becomes its own proof.

7·6 Reversibility and Audit Trails

Although Bitcoin Script is not reversible in the general sense, macro-expanded scripts are reproducible. Every output can be traced back to its originating macro, parameter, and loop index. This provides a direct mapping:

(opcode index) → (macro name, iteration index, source line)

This mapping enables full decompilation of the script into its high-level origin. It is the foundation of transparent computation: the idea that a smart contract should not only run deterministically, but explain itself.

Auditors, validators, and disputants can verify the exact rationale for every opcode. This is particularly crucial in legal contexts, where contract behaviour must not be inferred but demonstrated.

7·7 No Runtime Ambiguity: Flat Code as a Declarative Statement

In macro-expanded Bitcoin Script, the script no longer represents a program in the traditional sense. It is not a machine to be interpreted—it is a statement to be executed. It is:-

Finite

-

Non-branching

-

Pre-determined

All runtime ambiguity is eliminated. The validator node does not decide which path to follow; it walks a path already set. In this respect, macro-expanded scripts function more like proofs than programs. They encode the result of computation, not the process of discovering it.

This is the heart of Bitcoin's static model: no surprise behaviour, no runtime decisions, no branches, no cycles. All logic is exposed, all execution is traceable, and all results are explainable.

7·8 Summary

The execution semantics of macro-expanded Bitcoin Script are linear, deterministic, and fully auditable. Every opcode transforms a pair of stacks; every transformation is statically knowable. The script is not a dynamic computation but a static witness to a finite, deterministic process. Through symbolic execution, stack tracing, and source mapping, every step of contract logic becomes transparent. This is not just execution—it is verification by construction. The next section builds on this foundation to compare the static computational model of macro-expanded Bitcoin Script with traditional circuit-based representations of Turing computation, establishing its position in the broader theory of symbolic automata.


8. Turing Simulation in Finite Space: The Circuit Analogy

-

Comparison to hardware logic gates and FSMs

-

Description of finite tape Turing Machines and bounded computation

-

Role of compile-time expansion in symbolic Turing Machines

-

Constraint satisfaction: bounded RAM, bounded steps, bounded input

The expansion of iterative logic into static opcode sequences, as facilitated by --macro-unroll-loops, gives Bitcoin Script an unexpected affinity with a lineage of computational models not typically associated with stack machines—namely, Boolean circuits and symbolic automata. This section establishes the analogy between macro-expanded Bitcoin Script and circuit representations of Turing computation. It situates Script within the class of deterministic symbolic machines capable of simulating bounded-state Turing processes without dynamic control flow. The transformation from algorithm to literal form places Script at the intersection of computation and declaration, where logic becomes structure and program becomes artefact.

8·1 Turing Machines and Their Bounded Simulation

A Turing machine TT consists of a finite control set, a bidirectional tape, and a transition function that maps a pair of (state, symbol) into (new state, new symbol, head movement). The machine is defined by:

• An initial tape configuration

• A set of transition rules

• A halting condition

If we restrict T to a fixed input length kk, and a maximum number of steps t(k)t(k), we obtain a bounded Turing machine—a device that halts after finitely many transitions and reads no more than k cells. This bounded form is functionally equivalent to a circuit: every input has a single known output, and every transition can be encoded as a static combinator.

8·2 Boolean Circuits as Static Machines

A Boolean circuit is a finite, acyclic graph of logic gates. Each input bit propagates through gates until the output is reached. In theoretical computer science, a well-known result is that any Turing machine running in time t(n)t(n) on inputs of length nn can be simulated by a family of circuits {Cn}\{C_n\}, where each CnC_n has size polynomial in t(n)t(n). Each transition step of the Turing machine is emulated by a fixed layer in the circuit.

The machine is replaced by a literal object. There is no memory; there is no head. There is only a pre-defined path from input to output, enforced by structure.

This is precisely what macro-expanded Bitcoin Script becomes.

8·3 Bitcoin Script as Symbolic Circuit Realisation

Consider a Script that simulates tt steps of a Turing machine over an input of length kk. The macro compiler emits tt blocks of opcodes, each corresponding to a state transition:

Step 0 → OP_DUP OP_ADD OP_TOALTSTACK

Step 1 → OP_FROMALTSTACK OP_PUSH1 OP_ADD

Step 2 → …

Each block is fixed, parameterised at compile time, and emitted in order. The Script contains:

• The full state of the machine at every step

• All logic required to update symbols, positions, and states

• Zero branching, zero dynamic state, zero runtime evaluation

The execution is deterministic and acyclic. Script becomes a circuit.

8·4 Structure-Preserving Execution and Time-Expansion

This technique is a form of time expansion. Each iteration of the Turing machine becomes a flat segment in the Script. The space required to encode the computation grows linearly with t(k)t(k), and no further work is required by the interpreter.

The difference is this:

• A Turing machine performs the computation

• A Script records the computation

The validator does not explore multiple branches or simulate head movement; it simply executes the transcript, like replaying a log.

Thus, Script realises not computation per se, but the evidence of computation. It becomes the hashable, auditable, verifiable materialisation of a Turing process in bounded form.

8·5 Totality, Symbolic Closure, and Observable Behaviour

Every macro-expanded Script under --macro-unroll-loops represents a total function from input stack state to output stack state. It contains:

• No partial evaluation

• No runtime conditionals

• No nondeterminism

This allows one to define a mapping:

Evalₚ: Stack₀ → Stack_f

such that Evalₚ is injective, deterministic, and complete. Each Script is a finite symbolic artefact representing the function computed by a bounded Turing machine.

In other words, every macro-expanded Script is a realisation of a finite, symbolic computation trace. It is not a program—it is a statement.

8·6 Model-Theoretic Perspective: Script as Logical Encoding

From a model-theoretic standpoint, macro-expanded Bitcoin Script acts as a finite model of a logic program: a proof object that encodes all possible derivations of a bounded computation. This aligns Script with:

• Propositional proof-carrying code

• Logic-unrolling frameworks in bounded model checking

• Symbolic encodings of λ-calculus reductions

The interpreter becomes a verifier, and the Script becomes a chain of justified transformations, each step manifest, irreversible, and terminal.

This structural perspective is fundamental. It is what distinguishes Script from general-purpose runtimes, and aligns it with audit-oriented computing.

8·7 Comparison with Other Smart Contract Systems

Compare this with Ethereum's EVM:

• Ethereum contracts operate in a Turing-complete runtime with gas enforcement

• Execution involves runtime branching, dynamic storage, and opcode dispatch

• Result correctness depends on halting behaviour and gas limits

By contrast, macro-expanded Bitcoin Script:

• Is Turing-complete over bounded inputs without runtime cost unpredictability

• Contains no branching

• Is completely statically auditable

• Requires no special halting logic

Where Ethereum executes algorithms, Bitcoin Script encodes results. Where Ethereum re-runs code, Script replays decisions. This distinction makes Script ideal for high-assurance contracts, deterministic workflows, and legal verification.

8·8 Implications for Auditing and Security

Because every macro-expanded Script is a concrete artefact:

• It can be formally verified before being published

• It can be hashed, signed, and anchored

• It exposes no attack surface during execution

• It enables replay-verification by any observer

This model eliminates several classes of vulnerabilities associated with dynamic computation:

• Infinite loops

• Dynamic reentrancy

• Stack underflows from misbranching

• Behavioural divergence across clients

With Script, the only divergence possible is publication choice: whether or not a miner includes the transaction. Execution behaviour is fully determined.

8·9 Summary

Macro-expanded Bitcoin Script, under --macro-unroll-loops, is best understood not as a linearised program, but as a circuit: a finite, bounded, symbolic representation of a Turing computation. Each opcode becomes a gate; each stack state a wire; each execution a path through a fixed, acyclic structure. There is no runtime logic—only declared transformation. This structure elevates Script beyond scripting: it becomes a mechanism for committing logic, in finite space, forever. Deterministic, verifiable, terminal, and immutable.

This redefinition sets the stage for the next section, where we examine how this static structure contributes to security and verifiability in adversarial settings, and how macro expansion supports proofs of correctness and pre-execution auditing in live consensus systems.


9. Security and Verifiability: Benefits of Static Expansion

-

No dynamic jumps or runtime ambiguity

-

Constant-time evaluation path

-

Mitigation of DoS via bounded script depth

-

Easily auditable contract logic with no hidden branches

The decision to represent contract logic in a statically expanded form—using macro-unrolling to flatten all control structures into linear opcode sequences—carries profound consequences for both the security model and the verifiability of Bitcoin Script. The execution becomes not merely deterministic, but completely exposable, subject to pre-execution audit, and insulated from entire categories of runtime failure. In this section, we examine the security properties conferred by macro expansion, and show how it provides a disciplined architecture for constructing formally analysable, cryptographically anchored smart contracts on the Bitcoin ledger.

9·1 Determinism by Construction

The single most important security feature introduced by --macro-unroll-loops is removal of control flow ambiguity. A macro-expanded script contains:

– No OP_IF, OP_ELSE, or OP_ENDIF

– No OP_JUMP, OP_JUMPIF, or any program counter manipulation

– No variable loops

– No recursion

– No stack-dependent dispatch

Each opcode executes exactly once, in order. The program counter increments monotonically from 0 to |Π|. This ensures that the runtime behaviour of the script is identical under all clients, in all network conditions, on all validator nodes. There is no dependency on execution environment, consensus forks, or mempool behaviour.

This determinism is not enforced by runtime checks. It is embedded structurally by the compiler. The macro system collapses all symbolic logic into a fixed sequence. The final script is declarative, not algorithmic.

9·2 Exhaustive Auditability: Total Visibility of Logic

Because all iteration, branching, and symbolic parameterisation have been resolved at compile time, the entire logic of the contract is visible in its final opcode form. This permits:

– Full static analysis of every transformation step

– Pre-execution validation of stack height invariants

– Symbolic verification of input-output behaviour

– Byte-level correspondence to macro source

The final script becomes its own audit trail. There is no hidden code, no latent conditionals, and no control paths requiring dynamic analysis. This enables two critical properties:-

Complete Formal Verification

-

Regulatory Inspectability

Formal verification tools can consume the macro-expanded script, reconstruct its stack traces, and check that all contract predicates are satisfied on every input path. Likewise, legal and regulatory bodies can interpret the script in human-auditable form, compare it to the declared intent, and verify its completeness without running it.

9·3 Elimination of Runtime Failures and Exploits

Macro-expanded scripts remove the entire class of runtime failures associated with:

– Invalid branch conditions

– Deep recursion

– Infinite loops

– Stack underflows from misaligned conditionals

– Opcode reentrancy

– Nondeterministic state

All branching logic is statically resolved. All paths are precomputed. Stack depth is known at every step. No dynamic conditions are ever evaluated. Thus, the following are impossible in macro-expanded Bitcoin Script:

– Divergent behaviour between nodes

– Late-execution path discovery

– Gas exhaustion attacks

– Call-stack corruption

This is not theoretical. These are exactly the failure modes exploited in other smart contract platforms. The DAO bug on Ethereum, for example, was the result of runtime reentrancy—an error that cannot even be expressed in a macro-expanded Bitcoin script.

9·4 Pre-Signing Audits and Immutable Contract Logic

In standard Bitcoin contract construction, the script is embedded directly into the transaction’s unlocking or locking script field. Once mined, it is immutable. But unless the script is written in a macro-expanded, flat form, the complexity of understanding it grows with each layer of symbolic abstraction.

Macro expansion enables full pre-signing audits. The expanded script can be:

– Emitted alongside the source macro code

– Included in signature metadata

– Anchored by hash into ancillary on-chain or off-chain state

– Visually examined by contract auditors or legal verifiers

This satisfies the dual goals of contract transparency and legal enforceability. When a party signs a transaction that includes such a script, they are not agreeing to a possibility—they are agreeing to a trace. There are no surprises.

9·5 Execution Proofs and On-Chain Witnessing

Because the macro-expanded script contains all computational steps, the act of executing it is equivalent to verifying a witness. The input stack is a claim. The final stack is the result. The script enforces that the transformation from one to the other is exactly what was prescribed.

This enables several novel security primitives:

Proof-carrying transactions

On-chain logic proofs

Non-interactive zero-knowledge-like compression (if combined with Merkle-style scripting)

Rather than asking a miner to compute a result, the contract presents the result and proves that it was derived according to a static, verifiable procedure. The script becomes a cryptographic witness to its own correctness.

9·6 Isolation from Client Behaviour and Fork Risk

Because Bitcoin Script executes deterministically and does not depend on dynamic behaviour, macro-expanded contracts are immune to:

Fork-induced opcode divergence

– Client-specific opcode parsing

– Runtime environment inconsistencies

Each opcode in the final script is fixed. Each stack state transition is canonical. There are no version-dependent branches. This dramatically reduces the risk of consensus failure from smart contract behaviour and ensures that the contract executes identically in all validating environments.

Furthermore, because the macro expansion flattens all symbolic computation, no fork or policy change can retroactively alter the logic of the script—unless it disables an opcode explicitly used. Even then, the script hash will fail, and no invalid transaction will be accepted.

9·7 Compatibility with Policy Limits and Miner Filters

Security is not just about correctness. It is also about acceptability. Scripts that exceed miner policies are effectively insecure, as they will not be relayed or mined.

Macro expansion enables developers to:

– Quantify the exact opcode count and byte size of the script

– Optimise macro bodies to fit within known miner policies

– Warn when a script nears known consensus or policy limits

– Emit compiler diagnostics with estimated validation cost

This empowers contract authors to control their logic profile. A macro-expanded contract is not a black box. It is an engineering artefact.

9·8 Security as Structure: A Shift in Contract Philosophy

The security model enabled by macro expansion is not reactive—it is architectural. The discipline of forcing all computation into a finite, visible, verifiable structure removes the need for runtime defences. No guard clauses are needed. No exception handlers. No halting checks.

The script can only do what it says it will do. And it says everything.

This is a radical philosophical departure from mainstream smart contract platforms, which assume:

– Dynamic control flow

– Gas-based halting enforcement

– External calls

– State machines with reentrancy guards

In macro-expanded Bitcoin Script, none of these assumptions hold. The contract is not a dynamic system. It is a logical artefact. It is a computation already done, encoded as a sequence of validated steps.

9·9 Summary

Macro-expanded Bitcoin Script contracts are structurally secure, auditorially complete, and immune to entire categories of dynamic failure. They offer determinism not as an option, but as a guarantee. By eliminating control flow at runtime and replacing it with statically compiled logic, they enable total transparency, pre-execution audit, and proof-carrying computation. These properties make macro-expanded scripts not just safer than traditional smart contracts—they make them fundamentally more trustworthy.

The next section addresses the practical limitations of this model: what happens when the size of the logic exceeds miner policy, how bytecode growth interacts with script clarity, and how design strategies can balance structure with scalability.


10. Practical Limitations and Performance Trade-Offs

-

Script size limitations (max bytes per script)

-

Efficiency vs clarity in unrolling (readability trade-off)

-

Potential mitigations: code compression, shared prefix structures

-

Static analysis for loop body redundancy

The security and verifiability advantages of macro-expanded Bitcoin Script come at a calculable cost: bytecode growth. By transposing all control structures—iteration, branching, recursion—into statically unrolled sequences, macro expansion exchanges time complexity for space complexity. This trade-off is well understood in classical compiler theory, where loop unrolling and inlining are routinely employed for speed at the expense of code size. In the Bitcoin context, however, the constraint is not CPU-bound runtime but miner-imposed limits on transaction size and validation cost. Understanding how macro expansion scales, and how to manage its growth within the bounds of real-world deployment, is crucial for designing viable smart contracts in the original Bitcoin protocol.

10·1 Expansion Rate and Instruction Growth

Let a macro structure encode a logical loop of the form:

Loop(n, λi. B(i))

where n is the unrolling parameter and B(i) is the body parametrised by i. The macro compiler replaces this with a linear sequence:

B(0)‖B(1)‖⋯‖B(n – 1)

If the body B(i) emits k opcodes per iteration, then the total instruction count scales as:

|Π| = k · n

Thus, loop unrolling leads to linear growth in script size with respect to the logical iteration depth. For nested macros—say, a double loop with depth n × m—the growth is multiplicative:

|Π| = k · n · m

This is the macro expansion analogue of tensor product blowup: each level of iteration multiplies the size of the program.

10·2 Block Size as an Asymptotic Boundary

Bitcoin Script executes inside transaction contexts, and transactions live inside blocks. The ultimate boundary on script size is therefore the block size parameter σ. In Bitcoin SV (BSV), this limit is defined by consensus rules but configurable by miners and effectively unbounded in principle. In practice, however, scripts must remain within:

|Π| ≤ σ – ε

where ε accounts for transaction envelope, metadata, inputs, signatures, and other non-Script elements.

If a block has size σ = 4 GiB and the largest miner currently enforces a soft policy of accepting transactions no larger than τ = 10 MiB, then τ becomes the effective upper bound on script size for deployment purposes.

Under this limit, a macro-expanded script must obey:

|Π| < τ

meaning the unrolled logic must be compact enough to fit within this envelope. The implication is not that macro expansion is unviable, but that expansion depth must be engineered with care.

10·3 Compression Trade-offs and Data Repetition

Unlike higher-level programming languages that support function calls and symbolic parameterisation, macro-expanded Bitcoin Script consists entirely of literal instructions. This leads to repetition: each iteration emits a full copy of the body, even if 90% of its structure is invariant across instances.

Let B(i) consist of k₁ fixed opcodes and k₂ parameterised opcodes. Then the macro emits:

|Π| = n · (k₁ + k₂)

Despite k₁ being constant across i, there is no way to compress it unless a separate reusable macro expansion engine is implemented in pre-processing, since Script itself lacks facilities for abstraction. Consequently, bytecode redundancy is structural, not incidental.

This overhead can be mitigated by macro body optimisation at the compiler level, e.g.:

– Eliminate invariant sub-bodies

– Factor repeated opcode sequences into shared macro calls during code generation

– Flatten constant sub-computations during compilation

These techniques reduce real growth from O(n) to O(f(n)) for a reduced function f.

10·4 Opcode Density and Script Structure

Not all opcodes contribute equally to script size. For example:

– OP_PUSHBYTES_75 consumes 1 byte plus up to 75 bytes of data

– OP_PUSHDATA1 and OP_PUSHDATA2 consume additional prefix bytes

– Data-heavy scripts expand faster than control-heavy scripts

Moreover, OP_DUP, OP_ADD, OP_EQUALVERIFY, and other compact opcodes require only 1 byte each, allowing high instruction density. A script comprised solely of 1-byte opcodes can, in theory, include nearly 10 million instructions in a 10 MB transaction.

Thus, complexity per byte varies sharply depending on the opcode profile. Contracts relying on lightweight arithmetic or logical predicates can achieve far deeper expansion than those embedding large data blobs or multi-byte constants.

10·5 Cost Model: Validation Time vs Code Size

The trade-off for increased code size is generally linear validation time. In the BSV implementation, Script evaluation is conducted in a streaming, left-to-right fashion with no heap allocations and no need for backtracking. Each opcode is executed deterministically. Thus, validation cost grows with script size:

T_validate(Π) = c · |Π|

for some platform-dependent constant c. There is no combinatorial blowup, no dynamic dispatch, and no need to simulate a virtual machine state beyond the two stacks. This is fundamentally different from EVM or Wasm models, where branching, reentrancy, and gas estimation incur overhead.

Consequently, a macro-expanded contract of 1 million opcodes will validate in proportionally linear time and memory, and can be benchmarked precisely prior to deployment.

10·6 Unrolling Limits in Other Languages

It is instructive to contrast Bitcoin’s expansion model with those of conventional languages. In C or Rust, macro unrolling is constrained by compiler recursion limits, e.g. 256 levels for macro expansion in Rust. In hardware description languages (HDLs), unrolled loops generate combinational logic with area-cost trade-offs.

Bitcoin Script's macro expansion behaves analogously: the expanded form is hardware-like—it describes what is to be computed, not what might be computed. The bytecode is a hardware circuit flattened into bytes. Its scalability is architectural.

10·7 Strategies for Scalable Macro Design

To keep macro-expanded scripts within acceptable bounds, designers may:

– Limit loop depth to logarithmic ranges

– Use tree structures for balanced binary iteration

– Apply tail-folding techniques to merge repetitive subtrees

– Encapsulate proofs-of-computation in compressed witness forms (e.g. Merkle roots)

– Emit minimal stack-witnesses rather than full traces

For example, rather than include 10 000 multiplication steps, a macro may encode the verification of a claimed result via repeated squaring and stack-check assertions—thereby proving a trace without replaying it.

10·8 Beyond the Limit: Partitioning and Meta-Script Strategies

If a macro-expanded script exceeds miner policy limits, several mitigation strategies exist:-

Chained Contracts: Split logic into multiple transactions, chained by outputs and signatures, e.g. via OP_CHECKSIGFROMSTACK or OP_PUSH_TXHASH.

-

Merkleised Scripts: Encode macro expansions in off-chain Merkle trees with on-chain commitment and selective reveal.

-

Meta-Virtualisation: Treat Bitcoin Script as a bytecode host and encode logic in higher-order encodings (e.g. Rule110, tag systems) within data payloads, interpreted by static emulators.

Each approach trades bytecode simplicity for deployment flexibility and script modularity. Macro expansion is thus not a binary constraint, but a design surface.

10·9 Summary

Macro expansion induces linear or superlinear bytecode growth relative to logical depth. This is both a feature and a limit: it enables exact auditability at the cost of structural repetition. In Bitcoin SV, where no hard script-size limit exists save miner policy, the expansion depth is bounded only by design discipline and transaction size. Smart contract authors must treat macro expansion as a trade-off curve: verifiability grows with size, but deployment feasibility constrains size.

This space–verifiability asymptote defines the architectural sweet spot for high-assurance Bitcoin contracts. The next section explores the application of these principles in real-world contracts—from escrow protocols to token ledgers—and demonstrates how structure and scale can be balanced in a concrete, audited, on-chain logic model.


11. Advanced Constructs: Nested Macros, State Machines, and Simulation

-

Nested loop expansion (e.g., matrix operations, combinatorics)

-

State simulation via switch-case macro expansion

-

Conditional simulation with fixed depth logic trees

-

Emulation of register machines and arithmetic logic units

The conceptual and structural framework developed across the previous sections finds its culmination in practical contract architectures. These are not speculative constructs: they are executable, verifiable programs in Bitcoin Script that implement deterministic logic under bounded resource assumptions, without appeal to external oracles or mutable virtual machines. The macro compiler methodology enables the creation of deeply expressive, Turing-robust contracts whose properties are auditable before deployment and invariant under execution. This section surveys several canonical smart contract structures enabled or enhanced by macro-expansion within the original Bitcoin protocol (as preserved in BSV), explaining the mechanics, trade-offs, and verifiability profiles of each.

11·1 Finite-State Escrow Systems

A finite-state escrow contract is a state machine that accepts or rejects settlement instructions based on whether predefined temporal and logical conditions are met. Such a contract may, for instance:

– Accept a unilateral refund after n blocks

– Permit a bilateral payout contingent on both signatures

– Allow arbitration if no resolution within t steps

Under macro expansion, each state of the contract is encoded as a unique segment of script, with transitions unrolled into opcode sequences. For example:-

State₀ validates preconditions and redirects to either State₁ or State₂

-

State₁ enforces refund semantics with OP_CHECKLOCKTIMEVERIFY

-

State₂ enforces cooperative settlement via dual OP_CHECKSIG

Each transition is statically encoded. There are no dynamic conditionals: every branch of logic is expanded and embedded. The result is a script that implements the complete transition function of a DFA, with enforcement through deterministic stack conditions and signature verifications.

11·2 Token-Ledger Models Without Global State

The challenge of token implementation in Bitcoin stems from the lack of shared mutable state. Unlike Ethereum’s EVM, which offers global storage, Bitcoin contracts operate within local transaction scope. This constraint is sidestepped by encoding the token state into the outputs themselves, and validating its correctness during each spend.

Macro-expanded Script plays a central role here:-

The previous token UTXO encodes (owner, amount)

-

The spending script verifies that outputs represent a valid state transition:

– No tokens created or destroyed

– Sender authorised to transfer

– Totals conserved

This requires a complete circuit of validation logic for transfer, split, merge, and mint/burn scenarios—all encoded in Script. The macro compiler emits these validation routines, one per state transition class, generating a flat opcode representation with no runtime decisions.

By auditing the opcode sequence before deployment, the issuer ensures compliance and prevents category errors (e.g. negative balances or overflows). Each transaction becomes a cryptographic proof of lawful state change.

11·3 Contractual Payment Channels and State Commitments

Off-chain computation with on-chain enforcement demands mechanisms for locking funds under conditional release. The classic model is the payment channel: a pairwise arrangement in which one party commits value that can be incrementally spent by joint agreement or refunded unilaterally after timeout.

Macro-expanded contracts implement this as follows:

– Commitment: Locks value with OP_CHECKLOCKTIMEVERIFY and dual OP_CHECKSIG

– Update: Each update is a new transaction, pre-signed and invalidating the previous

– Close: Final agreed state is broadcast; fallback is timeout refund

The complexity lies in ensuring that each off-chain update can be verified on-chain under dispute. Here, macro expansion pre-emits the full logic for each possible closing state, including stack constraints, hashlocks, and sequence validation. This guarantees that any closure path is correctly enforced by the Script, without needing a separate interpreter or adjudication layer.

The compiled contract becomes an unambiguous machine: each path leads to one outcome, each stack configuration is final.

11·4 Discrete Log Contracts and Data-Oriented Conditions

In oracle-driven contracts such as discrete log contracts (DLCs), the payout depends on an external revelation, e.g. the price of an asset at a given block. Although Bitcoin Script lacks direct oracle integration, macro expansion allows encoding the entire space of possible oracle attestations and precomputing the valid redemption paths.

This involves:

– Commitment to all possible oracle signatures

– Embedding each as a separate branch in Script

– Using OP_IF–OP_ELSE cascades or macro-expanded OP_EQUALVERIFY sequences

Each conditional is replaced with a static validation clause, enabling script execution without runtime branching. This means that once the oracle publishes the attestation, the matching script branch executes automatically. The execution engine performs no interpretation: it merely checks signatures and stack values, exactly as instructed.

Because the contract is finite, all possible resolutions are enumerated at compile time. This transforms uncertainty into structural completeness.

11·5 Zero-Knowledge Verifiable Computations (ZKVC)

While true zero-knowledge proofs (e.g. SNARKs) are not natively supported in Script, macro expansion enables a practical analogue: verifiable computation with explicit stack constraints. That is, rather than hiding inputs, the contract enforces that a claimed computation result satisfies a deterministic check—without recomputing the entire function.

This is achieved via:

– A macro-expanded validation circuit

– Hash preimages, Merkle proofs, and computed values embedded as inputs

– Stack predicates verifying logical correctness (e.g. multiplication chains, modular inverses, hash equalities)

The verifier executes a bounded trace that checks consistency. The prover supplies the witness values, but the script ensures correctness. The compiler emits all necessary constraints as flattened opcodes.

This allows Bitcoin contracts to validate arbitrarily complex logic—modulo transaction size limits—without requiring runtime recomputation or VM-level branching.

11·6 Protocol Emulation and Computation on Public Data

Macro-expanded Script supports the implementation of small formal languages and micro-protocols on-chain. Examples include:

– Finite rule-based games (e.g. Tic-Tac-Toe, Prisoner's Dilemma)

– Tag systems or cellular automata

– Contractual compilers for small DSLs

Each logic system is encoded as a static transition table, with each move represented by a sequence of opcodes. Game moves or protocol steps are validated by ensuring they match legal transitions from the previous state.

In these cases, macro expansion is used to precompute all valid state moves. There is no interpreter loop. Instead, the transaction is the program, and validation is the machine. Execution becomes structural rather than procedural.

11·7 Programmable Proof-of-Work via Script Embedding

Proof-of-work can be extended beyond mining into contract design. For example, to redeem a coin, a user may be required to present a value x such that:

SHA256(x) = Y, with x < k

This embeds a work requirement into the contract. A macro-expanded script can be used to encode the comparison operation and enforce that the preimage x satisfies the difficulty target.

By using OP_SHA256 and explicit comparison logic, the contract performs bounded verification of effort. More generally, work-verified outputs can be used to prevent spam, model fair resource access, or incentivise honest computation.

The macro compiler precomputes the comparison structure and emits an evaluable circuit. There is no guesswork; only deterministic success or failure.

11·8 Summary

Macro expansion elevates Bitcoin Script from a minimalist scripting utility to a rigorous formal language suitable for high-assurance smart contracts. Its lack of dynamic features is not a weakness—it is a foundation for deterministic correctness. By encoding all computation structurally, before deployment, macro-expanded contracts transform the validation environment into a circuit evaluator rather than a procedural engine.

Escrows, tokens, payment channels, oracle systems, and proof-bearing scripts are not hypothetical in this model. They are already possible, already provable, and already live—waiting only for adoption. The language is finite, the semantics are closed, and the architecture is global.

Section 12 will address the final boundary: verification. If macro expansion allows us to encode contract logic exactly, how do we prove that it behaves as intended? And can we do so without running it? The next section answers in the affirmative.


12. Conclusion: Toward Symbolic Turing Completeness in Bitcoin Contracts

-

Bitcoin Script’s true power lies in deterministic symbolic logic

-

Loop macros are a gateway to structured contract automation

-

Compile-time expansion allows auditable, formal, bounded contract evaluation

-

This macro compiler model is a viable bridge from high-level contract logic to low-level verifiable execution


13 Compositional Structures and Contract Languages: Structural Recursion and Categorical Design in Bitcoin Script

To scale the semantic depth and verification rigour of Bitcoin contracts beyond isolated scripts, it is necessary to organise them as composable structures: families of logically related contracts, unified schemas for reuse and validation, and macro libraries capable of abstract generativity. This section formalises that organising principle. We show that Bitcoin Script contracts, once unrolled through macro-expansion, instantiate a typed, recursive space of contract forms, one that admits compositional reasoning, inductive verification, and categorical abstraction. In particular, we draw from structural recursion over typed trees, monoidal categories, and initial algebra semantics to formalise contract composition as a theory of code-as-data.

13·1 The Shift from Individual Scripts to Structured Contract Languages

A script is a single sequence of opcodes interpretable by the BSV virtual machine. A contract in this setting is a structured abstraction: a parameterised schema that expands to one or more such scripts under well-defined conditions.

A contract language is the collection of such schemas—possibly recursive, composable, and typed—that can be compiled deterministically into Script bytecode and simultaneously validated through structural invariants.

Each contract schema corresponds to a code generator and logical verifier. The generator composes basic macros into complex templates; the verifier ensures that each composite contract obeys stack discipline, satisfies pre- and post-conditions, and compiles to a terminating sequence under the rules of the Bitcoin VM.

13·2 Structural Recursion over Contract Trees

Macro-expanded contracts form inductively defined trees. Each contract is either:-

A leaf node: a primitive clause (e.g., CHECKSIG, CHECKLOCKTIMEVERIFY, or an arithmetic predicate), or

-

A composite node: a contract formed by sequencing, branching, or nesting child contracts under a higher-order operator (e.g., IF/ELSE, repeated applications, multi-path commitment).

Formally, define a contract algebra:-

Let C ::= Primitive(opcode_sequence) | Seq(C₁, C₂) | Branch(C₁, C₂) | Repeat(n, C) | Param(λx. C(x))

Then a macro-expanded Script is the result of folding over such a tree:

Compile : C → Script

Compile(Primitive(p)) = p

Compile(Seq(c₁, c₂)) = Compile(c₁) ++ Compile(c₂)

Compile(Branch(c₁, c₂)) = [OP_IF] ++ Compile(c₁) ++ [OP_ELSE] ++ Compile(c₂) ++ [OP_ENDIF]

Compile(Repeat(n, c)) = Compile(c) ++ Compile(c) ++ ⋯ (n times)

Compile(Param(λx. C(x))) = λx. Compile(C(x))

Because this recursion is structural, it is inductively defined and total. Every well-formed contract compiles to a finite Script, and every contract’s logical meaning can be reasoned about through the contract’s tree structure.

13·3 Typed Contract Schemas and Parametric Abstraction

Contracts must respect the stack discipline of the Bitcoin VM. To support large-scale composition and reuse, we define each macro as a typed morphism over stack shapes.

Let S be the set of all finite stack types. Then each macro M has a typing:

M : S_in → S_out

For example:-

CHECKSIG : (sig, pub) :: S → bool :: S

-

DUP : a :: S → a, a :: S

-

ADD : a, b :: S → a+b :: S

Contract schemas are then defined as parametric types:

Escrow(pub₁, pub₂, arb, t) : () → bool

MultiSig(n, [pub₁,…,pubₙ]) : ([sig₁,…,sigₙ]) → bool

TimeLock(t) : () → bool

Composition requires type alignment. For two macros f : A → B and g : B → C, the composition g ∘ f is well-typed and forms a higher-order macro A → C.

This type structure allows contracts to be statically checked at macro-expansion time, ensuring that no invalid stack transitions are present before the script is even emitted.

13·4 Monoidal Categories and Contract Composition

Let us frame macros and contracts categorically. Let 𝒞 be a category where:-

Objects are stack types S

-

Morphisms f : A → B are macros (code generators) from stack A to B

-

The identity morphism id : S → S is the no-op macro

-

Composition ∘ is macro chaining

This category is monoidal: there exists a tensor product ⊗ corresponding to stack concatenation, and morphisms distribute over it.

Then:-

DUP ⊗ ADD = apply DUP to first component and ADD to second

-

Sequencing contracts c₁; c₂ is Compile(c₁) ∘ Compile(c₂)

Moreover, if we interpret contracts as effectful programs in a stack-based monad, we can model stack transitions as Kleisli morphisms in the monoidal category of stack effects.

This yields a formal structure where:-

Contract schemas are objects in the category 𝒞

-

Macro libraries are morphisms (arrows)

-

Composition is associativity-preserving and has identity

-

Contract families form functors over parameter spaces

13·5 Contract Families and Reusability via Higher-Kinded Macros

A contract family is a macro that takes parameters and produces contract schemas:

MultiEscrow(n : ℕ, parties : [PubKey₁,…,PubKeyₙ], arb : PubKey, t : ℕ)

↦ Escrow contract with n-of-n multisig and arbitration clause

This is a higher-kinded macro: its parameters are not just values but also types—lists, pubkey vectors, time constraints, option flags.

The compiler instantiates these macros by unfolding all branches at compile time, yielding a fully inlined Script with all values embedded.

Each family member is:-

Functionally deterministic

-

Hash-committable (via HASH160 or SHA256)

-

Stack-typed and statically checked

Families thus enable typed generic programming over Bitcoin Script, and the compiler acts as a partial evaluator over parameter space.

13·6 Categorical Reasoning for Formal Verification

Verification of complex contracts reduces to compositional reasoning:-

Each macro is a morphism with verified pre/post-conditions

-

Composition preserves correctness under morphism chaining

-

Structural induction proves properties of recursive families

-

Functorial laws ensure that transformations (e.g. macro inlining, script optimisation) preserve semantics

Verification tools can then:-

Type-check all contract schemas

-

Simulate execution over finite test stacks

-

Prove logical equivalence via symbolic rewriting

-

Enforce that hash-committed addresses expand to expected semantics

13·7 Towards a Contract Algebra

Define an algebraic theory 𝒜 of contracts:-

Base types: Bool, Int, Time, PubKey, Hash

-

Stack constructors: ::, []

-

Operators: AND, OR, IF, LOOP, SEQ

-

Recursion: via structural trees and bounded unfoldings

Then each macro expansion is a homomorphism:

Φ : 𝒜 → Script

Where Φ maps abstract contracts into Script bytecode under the constraint that Φ(c₁ ∘ c₂) = Φ(c₁) ++ Φ(c₂) and Φ(id) = [].

This algebraic structure enables reasoning at the symbolic level, allowing contract auditing and validation to occur before any script is generated.

13·8 Implications for Scalable Bitcoin Contract Engineering

This compositional, categorical, and typed framework transforms Bitcoin Script from an assembler-like opcode language into a rigorous, verifiable, functional contract calculus. It enables:-

Definition of high-level contract languages atop Script

-

Modular construction of contracts from libraries

-

Static verification of preconditions and postconditions

-

Formal composition and reasoning via category theory

-

Deterministic unrolling of address commitments into verifiable structure

Far from being a limitation, Script’s apparent austerity becomes its strength. Under macro expansion and categorical semantics, it becomes a low-level target for an entire spectrum of typed, reusable, verifiable smart contract languages, all executable under the original Bitcoin protocol.


14 Macro-Driven Address Construction and Client-Side Unrolling

The classical Bitcoin address—originally a Base58-encoded representation of a 20-byte pay-to-pubkey-hash (P2PKH) locking script—can itself be viewed as a macro: a concise identifier that expands, in the validating node, into the four-opcode sequence OP_DUP OP_HASH160 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG. Everything since 2009—P2SH, P2WSH, P2TR—follows the same pattern: a compact address string encodes (a) a version byte, (b) a payload that is either a hash of a script or a direct commitment to pubkey data, and (c) a checksum. The macro compiler paradigm generalises this idea: any address can be treated as a macro handle that unrolls into an explicit script template when coins are spent. This section details how new “extra areas” of logic—custom covenant clauses, data commitments, multi-stage spending conditions—can be rolled into macro functions, embedded in wallet descriptors, and deterministically expanded on both client and node sides.

14·1 Addresses as First-Class Macro Handles

Classical template

addr_P2PKH(pubkey_hash) ⇒

OP_DUP OP_HASH160 pubkey_hash OP_EQUALVERIFY OP_CHECKSIG

General macro form

addr_KIND(parameters) ⇒

template_KIND(parameters) // emitted as Script

Where KIND may be ESCROW, TOKEN_TRANSFER, DLC_ORACLE, etc. The address layer merely commits to a hash of the expanded template; the actual template is revealed and executed when the output is spent.

14·2 Hash-Commitment Strategies

-

P2SH-style (legacy)

– Address payload = HASH160(redeem_script)

– Unlocking script pushes redeem_script then witnesses

-

P2WSH-style (SegWit/BSV analogue)

– Address payload = SHA256(witness_script)

– Witness field carries witness_script and stack items

-

Taproot leaf commitment

– Address payload = x-coordinate of tweaked internal key

– Spending path selects leaf script via Merkle proof

Under macro expansion, the redeem/witness/leaf script is generated by the compiler from high-level descriptors. The wallet retains both the template and its hash; the network sees only the hash until spend time.

14·3 Macro Address Compiler

A wallet-integrated address compiler operates in three passes:-

Descriptor parse

Example descriptor:

escrow(arb_pub, partyA_pub, partyB_pub, locktime=144)

-

Macro expansion

Converts to explicit Script:

OP_IF

2 partyA_pub partyB_pub 2 OP_CHECKMULTISIG

OP_ELSE

locktime OP_CHECKLOCKTIMEVERIFY OP_DROP arb_pub OP_CHECKSIG

OP_ENDIF

-

Hash-encode

Hash script → 20-byte or 32-byte digest → encode to Base58 or Bech32 address

The compiler records (descriptor, script, hash) triples in wallet metadata so that signing and later unrolling remain deterministic.

14·4 Client-Side Unrolling and Signing Workflow

When spending an output tied to a macro address:-

The wallet locates the triple by address hash.

-

It reconstructs the full Script from stored descriptor.

-

It permutes signing parameters: which branch, which pubkey, which locktime.

-

It generates signatures and witness stack exactly matching the macro’s post-expansion expectations.

– For the escrow example, if both parties sign: provide 0 dummy, sigA, sigB, OP_TRUE.

– If arbitration path: provide arb_sig, OP_FALSE.

Because the macro template is identical to what will be executed by nodes, no runtime mismatch can occur. Any signing failure surfaces at compile time in the wallet, not at broadcast time in the network.

14·5 Extensible “Extra Areas” in Wallet UX

Wallet software can expose parameterised forms:

• Build time-locked refund

• Embed registry commitment

• Attach oracle stipulation

Each form corresponds to a macro descriptor. Users fill fields (pubkeys, locktime, oracle key, data hash). The UI then:

– Calls the macro compiler

– Displays the resulting address for funding

– Stores expansion metadata for future spends

No manual Script editing is required. Power users may view raw opcodes; ordinary users interact with forms.

14·6 Composite Scripts and Multi-Function Outputs

An output can embody several independent obligations by concatenating macro templates under a single hash commitment. Example:

token_transfer(owner_hash, token_meta)

‖ royalty_clause(creator_pub, rate=5%)

‖ compliance_lock(kyc_pub, expiry=210000)

The compiler merges these into a single witness script. Each sub-macro contributes a segment; stack effect analysis guarantees overall correctness. At spend time, the unlocking witness must satisfy all segments—multi-stack predicates chain sequentially.

14·7 Deterministic Upgrade Paths

To evolve a macro family without breaking funds already in circulation:-

Version byte in descriptor

escrow_v2(…) → distinct script hash

-

Feature flags in payload

Low-order bits toggle optional sub-clauses

-

Wallet lookup by (hash, version)

Older funds keep expansion v1; new funds use v2

Because each version compiles to a different hash, coexistence is trivial. Funds remain spendable as long as the wallet retains the descriptor metadata.

14·8 Client-Side Simulation and Safety Nets

A robust wallet embeds a Script emulator:

– Replays the expanded script with proposed witness stack

– Confirms final stack predicate is truthy

– Rejects transaction building if emulator fails

This simulation mirrors node execution and removes the chance of broadcasting invalid spends. Since macro-expanded scripts are straight-line, emulation overhead is negligible.

14·9 Future-Facing Macro Libraries

To foster ecosystem reuse, macro descriptors can be packaged as libraries:

import escrow

import token

import oracle

out1 = escrow.build(arb, A, B, 144)

out2 = token.mint(issuer, supply, meta_hash)

out3 = oracle.lock(oracle_pub, event_hash)

The library provides:

– Descriptor grammar

– Compiler hooks

– Validation schemas (JSON-Schema or Protobuf)

– Integration tests with sample witness stacks

Open-source libraries allow auditors to verify templates once and reuse them across wallets.

14·10 Summary

Macro expansion does not stop at loops; it permeates the entire address layer. An address becomes a macro handle—compact, hash-committed, and client-expandable. Wallets compile descriptors into full scripts, track metadata, simulate spends, and guarantee that every output is spendable exactly under the intended conditions. By treating addresses as macro indices and scripts as fully unrolled logic, the ecosystem gains deterministic safety, upgrade flexibility, and human-readable contract definition—all while remaining 100 % compatible with the original Bitcoin protocol.


15 Deterministic Construction, Deferred Semantics, and Template Checksums: Deep Client-Side Logic in Contract Encoding

Bitcoin’s original architecture entails a profound bifurcation between execution semantics, which are universal and validator-bound, and construction semantics, which are local, client-defined, and open-ended. While the validation layer remains invariant and minimal—designed around stack-based Script evaluation with no dynamic loading, no runtime dispatch, and no opcode introspection—the expressive power of the system is concentrated in the pre-execution phase. This phase resides entirely on the client side, where wallet software orchestrates macro-expansion, structural versioning, template parameterisation, and logical binding. This section formalises the maximal power of this separation, culminating in a fully deterministic and check-summable macro-template pipeline that allows complex contract logic to evolve without any miner awareness or protocol change.

15·1 Bitcoin Addresses as Deferred Scripts: The Hash as a Template Checksum

The canonical Pay-to-Public-Key-Hash (P2PKH) address encodes not semantics, but the digest of a public key: hash160(P). When this is embedded in a standard locking script:

OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG

the miner validates the spending input against the full public key P and the signature sig, but the address hash itself acts as a binding anchor—not a piece of executable logic.

This construction is not validated directly by the node; rather, the wallet is responsible for:-

Hashing the user’s public key into an address;

-

Mapping incoming payments to script templates;

-

Ensuring that the expected unrolled script will reconstruct to a known semantic form;

-

Validating that the hash matches the intended contract.

Hence, even Bitcoin’s standard address model is an example of client-side deferred semantics, where the on-chain representation is a digest of a full structure known only locally.

This model generalises.

15·2 Generalised Script Templates with Structural Checksum Anchors

A script template T can be treated as a macro function:

T: Params → Script

Let π = T(p₁,…,pₙ) be the fully expanded script under parameter vector p⃗. We define a checksum:

chk = HASH160(π)

This chk can be used as a binding anchor in a variety of forms:-

P2SH (OP_HASH160 OP_EQUAL)

-

P2WSH (OP_0 under SegWit)

-

Custom tag-encodings for embedded wallets or dApp interpreters

The key point: the node validates π at execution, but does not interpret T or p⃗. It receives only the expanded bytecode and its digest. The meaning of π exists solely within the wallet’s macro-template library.

This is the same as the address model: the wallet knows the public key and derives hash160(P), but the miner sees only the hash and verifies consistency at redemption time.

This mechanism scales to arbitrary contract logic.

15·3 Versioned Macro Libraries and Deterministic Template Compilation

To support composable contract families, wallets must expose:-

A versioned macro registry: each template is bound to a semantic version;

-

A structural invariant: each version emits scripts with well-typed stack effects and finite expansions;

-

A deterministic compiler: macro expansion is a pure function from (template, version, parameters) → script.

Given these invariants, we may define a Template Descriptor:

Descriptor := (TemplateName, VersionTag, ParamVector)

This tuple is passed to the client-side compiler, producing a script π and a digest chk = HASH160(π). The digest acts as a universal contract identifier, anchoring payments, tracking redemptions, and enforcing contractual matching. The descriptor never appears on-chain.

Miners, again, are blind to this logic.

15·4 Multiple Client Implementations: Semantic Divergence and Reconciliation

Suppose two wallets implement the same template T, but with differing version logic.-

Wallet_A uses T_v1, with expansion π₁

-

Wallet_B uses T_v2, with expansion π₂

These templates may differ in timeout logic, fallback clauses, or arbitration rules. Their digest anchors chk₁ = HASH160(π₁) and chk₂ = HASH160(π₂) will differ. This disjointness is intentional.

Recipients can distinguish contract variants simply by address. The wallet knows, from the received address, which macro logic to invoke. No dynamic inspection or decoding is needed. This creates a rich namespace of version-tagged contract classes, embedded via hash binding in the address layer.

Note: the node still sees only the expanded script.

The existence of version divergence is fully encapsulated client-side.

15·5 Forward Compatibility: Unrolled Scripts as Canonical Representations

To maintain compatibility across wallet versions:-

Every compiled script π should be serialised deterministically;

-

Every macro expansion must be idempotent with respect to its input descriptor;

-

Version upgrades must deprecate but not reinterpret prior descriptors;

-

Structural upgrades may compose: T_v2 = T_v1 ∘ Patch_v2, with audit log

Thus, π is not merely executable code, but a canonical record of a contract instance. It may be stored, hashed, compared, version-tracked, and semantically interpreted by any client who knows the template.

From the node’s perspective: still just bytecode.

From the client’s perspective: a semantically tagged, versioned contract instance with provenance.

15·6 Checksums as Contract Integrity Markers

Every template script has an integrity checksum:

chk = HASH160(π)

This checksum functions as:-

A binding hash in addresses (e.g., P2SH, P2WSH);

-

A contract ID for logging, auditing, and indexing;

-

A wallet recovery key, enabling semantic reconstruction of contract terms;

-

A version tracker, uniquely identifying the expansion semantics used.

Checksums are not validated by nodes except during redemption: at which point the spending input must supply π such that HASH160(π) = chk.

Thus, all structure exists outside the protocol.

15·7 Embedding Structural Semantics in Wallet Interfaces

Wallet UIs become the interface for contract families. Users do not read bytecode. They interact with:-

Contract templates;

-

Parameter forms;

-

Logical clauses;

-

Version histories.

The wallet backend compiles these into concrete scripts. It uses the checksum to:-

Display meaningful status ("Escrow v2, Alice→Bob, Timeout = 30 days");

-

Recover known descriptors;

-

Simulate outcomes (e.g., on timeout, Bob may claim funds);

-

Validate that incoming transactions match expected contract types.

The miner does none of this.

15·8 Implications: A Secure, Layered Model for Programmability

This architecture has deep implications.-

Security: The script interpreter remains minimal. No dynamic loading, no soft forks required. All evolution is client-side.

-

Auditability: Scripts are deterministic. Every template version emits a unique hash.

-

Upgradeability: Wallets can retire old templates, add new ones, simulate and compare versions.

-

Universality: Every contract becomes a hash-identifiable artefact, like a digital object with structural provenance.

Bitcoin, under this model, becomes not merely programmable—but versioned, auditable, and semantically layered.

The node validates what it sees. The client understands what it means. And the separation between meaning and enforcement is not a weakness—it is the very foundation of programmable cash.


16 Conclusion: Structural Determinism, Macro-Expanded Computability, and the Role of Wallets in Script Semantics

Bitcoin’s original design, as instantiated in the implementation lineage preserved by BSV, exhibits a subtle and rigorous computational model rooted in deterministic structure, not dynamic control. The entirety of this treatise has established the claim that Bitcoin Script, properly understood, forms a symbolic, compile-time Turing-equivalent system—not by runtime generality, but by exhaustive static generation under finite constraint. The language of Bitcoin Script, with its twin stacks and opcode algebra, is not a crippled subset of computation, but a deterministic, replay-safe substrate for provably bounded evaluation.

By grounding the design in the classical model of two-stack deterministic pushdown automata (2PDA), we demonstrated that Bitcoin Script possesses—when unencumbered by post-2010 BTC-imposed restrictions—a semantic capacity equivalent to any Turing machine with a known input length and fixed resource bound. This includes full simulation of multi-register arithmetic, nested logical predicates, and even bounded temporal systems. The role of macro expansion, executed entirely on the client side, allows for loop unrolling, conditional resolution, and branching to be symbolically realised, resulting in a single flat script digestible by miners without dynamic interpretation.

The practical consequence is that the wallet becomes the compiler. The wallet is where contract logic lives, where templates are instantiated, and where versioning and macro-recursion are unfolded into concrete scripts. This distinction—between construction semantics (client side) and validation semantics (network side)—is the architectural linchpin that enables infinite composability without increasing the complexity of consensus code.


Summary of Sections

-

2PDA Foundations: We established that Script’s two-stack model, when interpreted deterministically and with sufficient memory, simulates a full Turing machine within finite bounds.

-

Theoretical Equivalence: BTC truncated the language artificially; BSV restored the original form, maintaining opcode completeness and removing extraneous script size and opcode limits. This restoration retains the Turing-equivalent theoretical substrate.

-

Macro Unrolling: Macro-expanded loops, iterations, and recursive expansions are evaluated at compile time. Runtime is purely linear.

-

Arithmetic Closure: All arithmetic operations, including multiplication and modular division, are available in BSV. BTC disabled these. Macro-level simulation remains possible even where primitives are absent.

-

Functional Schemata: The structure of reusable macro libraries enables parameterised templates to be statically expanded with strict type and resource bounds.

-

Circuit Analogy: Bitcoin scripts simulate finite-tape circuits. Contracts are best understood as bounded-time programs or Boolean circuit analogues.

-

Macro Composition and Scoping: Modular construction of script functions leads to contract families, version-tagged templates, and nested semantic modules that preserve determinism.

-

Enumerated Execution: Execution is path-linear and stack-transformative. All side effects are stack-bound, permitting rigorous pre-execution simulation.

-

Type Safety: Macros encode stack effects, enabling statically typed Bitcoin Script. Contract libraries become provably safe under abstract interpretation.

-

Client-Side Compilation: All high-level control is flattened client-side. No dynamic dispatch, no loops, no recursion. Yet complexity scales through expansion.

-

Versioned Templates: Semantic upgrades occur through template versioning, not protocol change. Script hashes are invariant identifiers; the meaning resides off-chain.

-

Semantic Anchoring: Contracts are referred to by hashes of their compiled forms. Wallets link contract logic to addresses; the node sees only the digest.

-

Language Composition: Families of contract macros, parameterised libraries, and recursive schemes create a structured, composable domain-specific language (DSL) for smart contracts.

-

Script Hashes and Addresses: The standard address system already uses this principle: wallet logic builds a script, hashes it, and uses the hash as a spending address. Contracts can follow the same method.

-

Client Logic and Checksum Anchors: All richness lives at the wallet layer. Script digests bind semantics; miners need not understand the template. Structural hashes function as canonical contract identifiers.


Final Analysis

Bitcoin Script, when interpreted via its original specification, is not a sandboxed toy, but a formally bounded, stack-based programming language capable of expressing any computational behaviour within predefined resource limits. The key constraint—total determinism and statelessness—does not neuter expressiveness; it relocates it to a higher layer: the wallet.

This client-side layer controls contract composition, parameterisation, macro expansion, and semantic tagging. It version-tracks templates. It resolves loops. It validates stack effects. It ensures that the unrolled script is executable and auditable. And it does all this before any node sees the script.

Miners and nodes never need to evolve. They execute only what they receive. They verify only concrete, deterministic scripts.

This design is not a limitation. It is the foundation of protocol invariance.

By lifting programmability above the consensus layer, Bitcoin achieves the ultimate paradox: a language that is both fixed and unlimited. No hard forks are required for evolution. The system expands not by changing the engine, but by reprogramming the compiler.

Such is the elegance of Bitcoin’s original design.


17 Comprehensive Conclusion: Deterministic Bounded-Computation, Macro Unrolling, and Wallet-Centric Contract Engineering

Over the preceding sections we demonstrated—step by step—that Bitcoin Script (in its original, unrestricted form retained by BSV) constitutes a deterministic, two-stack pushdown automaton powerful enough to simulate any finite-tape Turing machine once every loop, branch, and recursion is resolved at compile time. Loop macros, static conditionals, and template versioning shift all complexity to the client side. Miners receive a single, flat opcode stream; they do not interpret templates, version tags, or control structures. This division of labour secures consensus, simplifies verification, and unleashes unlimited expressiveness at the wallet layer.


A wallet-first compilation pipeline

-

Descriptor entry – A user (or dApp) selects a template such as Escrow.v3, fills parameters, and clicks “Generate”.

-

Macro expansion – The wallet compiles every loop (LOOP, REPEAT, nested RANGE) and inlines every conditional. If the target network disables an opcode, the wallet substitutes an emulated sequence or pre-computed constant.

-

Script hash & address – The resulting script is hashed (HASH160 or SHA256) and encoded into a P2SH/P2WSH address.

-

Funding – Coins are sent to that address; the chain records only the digest, not the template.

-

Spending – When redemption is attempted, the wallet resurrects the template, re-expands it (guaranteed deterministic), assembles the witness stack, emulates the full run locally, and finally broadcasts the transaction that contains the fully unrolled script.

-

Miner validation – Nodes validate only the flat bytecode. They do not know—and need not know—that it originated from Escrow.v3.

Because the script is straight-line code, every node, explorer, or court can replay the evaluation exactly and verify its single post-condition (usually “leave one truthy item on the stack”).


Theoretical pillars revisited

-

Two-stack PDA equivalence – Main stack + alt stack deliver Turing completeness when the computation is unrolled and finite.

-

No dynamic loops – Runtime opcodes like OP_JUMP or conditional loopbacks are unnecessary; bounded iteration is represented spatially.

-

Circuit analogy – A macro-expanded script is a time-unfolded Boolean circuit: execution is merely walking the wires.

-

Security – Removal of branches eliminates re-entrancy, gas exhaustion, and non-deterministic faults. Every possible path is visible prior to broadcast.

-

Versioned templates – Different wallets may implement Token.v1 and Token.v2; their hashes differ, so outputs cannot be confused. Compatibility problems never reach the chain.


Canonical opcode snippet

Below is the definitive, wallet-generated expansion for a “square numbers 0–2” loop; the first variant uses the BSV-only OP_MUL, the second is BTC-compatible via pre-computed constants. Both are ready for miner evaluation as-is.

BSV (OP_MUL available)

OP_0 OP_DUP OP_MUL # 0·0 = 0

OP_1 OP_DUP OP_MUL # 1·1 = 1

OP_2 OP_DUP OP_MUL # 2·2 = 4

BTC (OP_MUL disabled – push literal results)

OP_0 # 0

OP_1 # 1

0x01 0x04 # raw PUSHDATA1 0x04 (value 4)

Wallet logic guarantees that each integer above 16 would instead be emitted with a proper PUSHDATA prefix (OP_PUSHDATA1, OP_PUSHDATA2, etc.), never the invalid mnemonic OP_PUSH4.


Implementation ease inside wallets

-

Template registry – JSON or SQLite table {name, version, macro_source, hash}.

-

Pure functions – Every template compiles with no side effects; identical parameters always yield identical scripts and hashes.

-

Pre-flight emulator – A 200-line stack machine can replay any unrolled script for sanity checks before broadcast.

-

Upgrade path – New template versions simply append rows; old funds remain spendable because the wallet looks up by hash.

-

Checksum UX – When displaying an address, the wallet shows “Template Escrow.v3, timeout 144 blocks” instead of a cryptic Base58 string, because it owns the descriptor.


Final synthesis

Bitcoin’s consensus code never had to chase feature creep. By pinning all advanced behaviour to deterministic, pre-expanded bytecode, the network remains agnostic to contract innovation. Wallets act as compilers, auditors, and simulators—binding user intent to on-chain truth through hash-committed templates. Every complex contract decomposes to a proof object: a single, linearised Script whose validity is machine-checkable by any node, forever.

In short: structure lives at the edge, certainty lives in the core. Loop macros, template versioning, and deterministic expansion turn Bitcoin Script into a formally bounded, Turing-robust language whose complexity never threatens consensus—because consensus never sees it.


Appendix A – Clarifications and Nuances

Finite-Space Turing Completeness

Throughout the treatise each reference to “Turing-complete” logic must be read as “Turing-complete over a bounded tape and a finite execution trace”.

A two-stack push-down automaton is functionally equivalent to a universal Turing machine only when its stacks are permitted to grow without limit. Bitcoin Script inherits the model but not the infinite medium: the script itself is finite, and the stacks cannot outgrow the bytes committed to the transaction. Every occurrence of the phrase “simulating Turing-complete computation” should therefore be understood to imply this bounded-space caveat; the text already does so in most places, yet the qualifier is now stated explicitly for absolute precision.

Every physical computational system, including those described as “Turing-complete,” operates under finite resources—bounded space, finite memory, and real-world time constraints. The abstraction of Turing completeness presumes infinite tape and unbounded computation steps, but no instantiation in reality can ever satisfy these criteria. In this sense, Bitcoin Script, when equipped with macro expansion and unrestricted opcode sets as in the original protocol, attains practical Turing completeness: it can compute any function that halts within the resource bounds of the system. To call such a system “not Turing-complete” is to conflate theoretical infinitude with implementable capability. Every computation in the real world halts, and Bitcoin’s model simply admits this truth formally.

Theoretical versus Practical Unboundedness

Section 10 explains that, under BSV rules, neither script length nor opcode count is capped by consensus. Nevertheless, practical ceilings exist. At protocol level the only hard bound is the block size (currently 4 GiB), yet miners commonly publish far smaller policy limits—often on the order of 10 MB per transaction and a few million opcodes per script. These operational boundaries do not alter the theoretical argument, but they dictate real-world engineering choices.

Whenever “unconstrained in principle” is asserted, the reader should append “…subject to block size and miner policy in practice”.

“Unbounded Memory” in the 2PDA Analogy

Section 2.3 states that “the two stacks supply unbounded memory”. In the abstract automaton that is true: each stack may grow indefinitely. Inside Bitcoin, however, the stacks share the same memory budget as the rest of the script because they are populated exclusively by data already present in (or computed from) that script. Thus the memory is logically unbounded for purposes of completeness proofs, yet physically bounded by the enclosing transaction payload. This tension—model versus implementation—underpins the phrase “finite-space Turing completeness”.

The Deterministic Client-Side Compiler

Section 15.3 calls the wallet macro expander a “deterministic compiler”. That description is exact: given an immutable tuple (template name, version tag, parameter vector) the compiler always emits the same byte-identical script, without optimisation passes, dynamic linking, or runtime introspection. It is, in effect, a total function from descriptive metadata to concrete opcode sequence. I am clarifying this role to prevent confusion with traditional compilers that may rearrange code or perform speculative execution transforms.

Citation Mark-Up Consistency

A handful of earlier LaTeX-style citation markers were rendered inconsistently. The final manuscript uses plain Unicode references for section numbers and avoids bracketed numeric citations altogether, obviating the mismatch. I shall fix all this in time.

Appendix B – Canonical Encoding Rules, Virtual Opcode Guidelines, and Reference Macros

This appendix provides a self-contained specification for wallet-side compilers that transform high-level Bitcoin-Script macros into byte-perfect, consensus-compliant scripts for either BSV or BTC. Each section describes a specific encoding pitfall, states the consensus rule, and supplies reference-quality remedies—including tested Python-style helper functions and illustrative macro fragments.


B 1 Minimal Push Encoding for All Integers

Consensus requirement Any integer literal placed on the main stack must be encoded with the shortest possible representation.-

0 – 16 → dedicated opcodes OP_0 … OP_16.

-

-1 → OP_1NEGATE.

-

All other values → raw little-endian ScriptNum inside an appropriate PUSHDATA wrapper.

-

Positive values whose high byte ≥ 0x80 must be padded with 0x00.

-

Negative values must be sign-extended: the high bit of the most-significant byte set to 1; pad with 0x80 if needed.

def push_int(i: int) -> list[bytes | str]:

"""

Emit the canonical opcode/bytes sequence that pushes integer i.

Guarantees minimal ScriptNum form per consensus.

"""

Tiny ints

if 0 <= i <= 16:

return [f"OP_{i}"]

if i == -1:

return ["OP_1NEGATE"]

Absolute value bytes (little-endian, no sign)

abs_bytes = abs(i).to_bytes((abs(i).bit_length() + 7) // 8, 'little') or b'\x00'

if i < 0:

Ensure sign bit set

if abs_bytes[-1] & 0x80:

abs_bytes += b'\x80'

else:

abs_bytes = abs_bytes[:-1] + bytes([abs_bytes[-1] | 0x80])

else:

Positive: prepend 0x00 if high bit would signal negative

if abs_bytes[-1] & 0x80:

abs_bytes += b'\x00'

ln = len(abs_bytes)

if ln <= 75:

return [bytes([ln]) + abs_bytes]

if ln <= 255:

return [b'\x4c' + bytes([ln]) + abs_bytes] # OP_PUSHDATA1

if ln <= 65535:

return [b'\x4d' + ln.to_bytes(2, 'little') + abs_bytes] # OP_PUSHDATA2

raise ValueError("Integer too large for standard Script")

All higher-level macros must delegate integer emission to push_int.


B 2 Virtual Helper “Opcodes” (PUSH_TO_ALT, DUP_TO_ALT, …)

No consensus opcode combines a data push with an alt-stack transfer. Such conveniences must expand to standard opcodes before broadcast.

def push_to_alt(i: int) -> list:

"""Push minimal integer i, then move copy to alt-stack."""

return push_int(i) + ["OP_TOALTSTACK"]

def dup_to_alt() -> list:

"""Duplicate top of stack and move duplicate to alt-stack."""

return ["OP_DUP", "OP_TOALTSTACK"]

Assembler front-ends may display PUSH_TO_ALT(5) or DUP_TOALT for readability, but compiled bytecode must be the two-opcode canonical form.


B 3 Mandatory Canonical-Form Pass

After macro expansion, run a deterministic post-processor:-

Scan every literal push.

-

Re-encode using push_int logic.

-

Abort compilation if any encoding shrinks—this flags a non-minimal push that would violate policy.

This guarantees standardness on BTC and avoids malleability warnings on BSV.


B 4 Assembler Literals versus Bytecode Reality

Illustrative documentation often shows bare hex (0x04). Remember:-

0x04 alone in bytecode could mean OP_4 (an opcode).

-

To embed the value 0x04 as data you must prefix with a length byte (0x01 0x04) or use OP_4 if the semantic intent is integer 4.

Always decide explicitly whether a token is an opcode or PUSHDATA payload and encode accordingly.


B 5 Script-Level Examples

B 5·1 BTC-Compatible Square List (0² … 10²) – No Arithmetic Opcodes

def square_literal(i: int) -> list:

return push_int(i * i)

def unrolled_squares(n: int = 11) -> bytes:

out = []

for k in range(n):

out.extend(square_literal(k))

return b''.join(op if isinstance(op, bytes) else op.encode() for op in out)

script_bytes = unrolled_squares()

Safe under BTC and BSV standard policy.

B 5·2 BSV-Optimised Alt-Stack Preparation

def build_alt_block(values: list[int]) -> list:

script = []

for v in values:

script.extend(push_to_alt(v)) # virtual helper → 2 opcodes

return script


B 6 Compiler Test Suite (Essential Cases)

-

Literal fuzz – encode ±(2⁰ … 2¹⁶). Verify minimal size by round-trip parsing with btcdeb.

-

Sign corner cases – encode −1, −128, 128; assert correct sign bit handling.

-

Opcode whitelist – ensure output references only documented opcodes.

-

Determinism – compile twice, compare byte-for-byte equality.

-

Network accept – submit to regtest node with default policy, expect mempool success.

Optional extensions: compile under prior compiler versions, confirm checksum stability or intentional deltas.


B 7 Key Takeaways for Wallet Developers

-

Never fabricate exotic opcodes. All conveniences live strictly at macro level.

-

Always emit minimal ScriptNum encodings—positive and negative.

-

Run a canonicalisation pass to guarantee policy compliance.

-

Document every virtual helper so auditors can trace two-opcode expansions.

-

Include fuzz, sign, and network-acceptance tests in CI pipelines.

Adhering to these rules produces scripts that are byte-minimal, policy-standard, and deterministic—ready for reliable on-chain execution across both BTC and BSV networks.Subscribe


← Back to Substack Archive