Learn to build safe, expressive smart contracts on Bitcoin with readable, type-safe code.
Bithoven is a high-level, imperative programming language designed for Bitcoin smart contracts. It bridges the gap between complex contract logic and the low-level stack machine of the Bitcoin Virtual Machine (VM).
Unlike writing raw Bitcoin Scriptβwhich is notoriously difficult and
error-proneβBithoven allows you to write readable, auditable code with
modern control flow (if/else), named variables, and built-in safety checks. It compiles down to
highly optimized native Bitcoin Script.
Why Bithoven?
rust-miniscript.
OP_IF <1000> OP_CSV OP_DROP <alice_pk> OP_CHECKSIG OP_ELSE OP_HASH256 <hash> OP_EQUALVERIFY <bob_pk> OP_CHECKSIG OP_ENDIF
Stack-based, hard to read, error-prone
if condition { older 1000; return checksig(sig_alice, "<alice_pk>"); } else { verify sha256(sha256(preimage)) == "<hash>"; return checksig(sig_bob, "<bob_pk>"); }
Readable, type-safe, checked before deployment
v0.1.0 is a major revision of the analyzer and back end, accompanying the Bithoven paper.
signature item, and
only the literal shape
!checksig(..) was
flagged. v0.1.0 decides, for every path, whether it can succeed
while every checksig
fails. Rewrites that 0.0.x compiled into anyone-can-spend scripts,
such as
checksig(..) == false,
checksig(..) || flag
or an inverted
if checksig(..), are
now rejected.
!!checksig(..), which
0.0.x wrongly rejected, now compiles.
Details β
DeadPath instead of
compiling to an unspendable output.
if needs an
else, there is exactly
one stack declaration per spending path, and paths that split at a
branch must agree on the witness items consumed before it.
sig; 0.0.x rejected
this with
VariableConsumed.
verify,
return and
if operands must be
bool; byte strings are
capped at the 520-byte push limit; key literals must use the
target's encoding.
OP_NUMEQUAL, so an
overflowing sum can no longer compare equal byte-wise, and long
byte-string literals that could crash the 0.0.x compiler now
compile.
Upgrading from 0.0.x?
Contracts that relied on the looser rules may stop compiling. Give
every if an
else
(NoReturn), write
exactly one declaration per spending path
(DeclarationPathMismatch),
and use 32-byte x-only keys under
taproot or 33-byte
compressed keys under
legacy/segwit
(MalformedPubkey).
You can install the Bithoven compiler via the command line or package managers.
Try Bithoven instantly in your browser without installation:
Go to Bithoven Online IDE
A Bithoven contract (file extension
.bithoven) consists of three main parts:
return.
Pragmas define the compiler version and the Bitcoin protocol target.
The
target
controls opcode generation.
legacy: Generates P2SH-compatible
script. Key literals are 33-byte compressed keys.
segwit: Generates P2WSH-compatible
script. Key literals are 33-byte compressed keys.
taproot: Generates P2TR-compatible
script (uses
OP_CHECKSIGADD). Key
literals are 32-byte x-only keys.
A key literal in the wrong encoding for the target, or one that is
not a point on secp256k1, is rejected with
MalformedPubkey.
| Type | Description | Example |
|---|---|---|
| bool | Boolean values | true |
| number | Integer values (32-bit signed, Β±2,147,483,647) | 1000 |
| string | Hex or ASCII string data (at most 520 bytes) | "secret" |
| signature | ECDSA (legacy/segwit) or Schnorr (taproot) signatures, supplied by the witness | sig_alice |
Public keys are not a declarable type. A key must be
a string literal such as
"0245...", so a spender
can never substitute a key of their own; a key taken from the witness
is rejected with
TypeMismatch.
Operands are type-checked at compile time; a mismatch is rejected
with InvalidOperation.
| Operators | Operands | Result |
|---|---|---|
| + - max(a, b) min(a, b) ++ -- negate abs | number | number |
| < <= > >= | number | bool |
| == != | Two operands of the same type: number, string or bool | bool |
| && || ! | bool | bool |
| sha256 ripemd160 | number, string or bool (not signature) | string |
| len | string | number |
| checksig | signature and key literal pairs | bool |
Bithoven allows you to define multiple valid "spending paths" at the top of your file. The compiler verifies that every path is secure.
The compiler enforces four rules on declarations:
returns appear in the
source (the if side
before the else side).
Otherwise:
DeclarationPathMismatch.
InvalidConsumptionOrder.
condition). Otherwise:
DeclarationPathMismatch.
UndefinedVariable,
VariableConsumed and
UnusedVariable catch
the alternatives.
π‘ A witness serializes the stack bottom-to-top, so when spending, the first declared item is the last one pushed.
Use standard if/else logic. Every
if needs an
else.
Halts execution if false.
Bithoven has no loops, recursion or calls, so a contract is a tree of
if/else
branches whose leaves are
returns. The compiler
requires that:
if has an
else, and an
if/else
is the last statement of its block; put checks shared by both
branches before it.
(NoReturn,
UnreachableCode)
return <bool>,
and nothing follows a
return.
(NoReturn,
UnreachableCode)
if,
verify and
return are
bool.
(InvalidOperation)
else { return false; },
is fine as long as some path can.
(NoSigRequired,
UselessSig,
DeadPath)
The compiler range-checks every locktime literal and rejects one out
of range with
IntegerOverflow.
n blocks. 0 β€ n β€ 65,535, the 16 bits
BIP-68 gives a relative locktime.
n. 0 β€ n β€ 4,294,967,295.
Hashing:
sha256(data),
ripemd160(data).
Nested hashes compile to single opcodes:
sha256(sha256(x))
becomes OP_HASH256 and
ripemd160(sha256(x))
becomes OP_HASH160.
Length:
len(s) returns the byte
length of a string.
Signature Checks: Supports single and multisig via
checksig. Keys
must be literals in the target's encoding. Multisig compiles to
OP_CHECKMULTISIG under
legacy/segwit
and to a chain of
OP_CHECKSIGADD under
taproot.
Bithoven is "Correct-by-Construction." A contract reaches the code generator only after passing all four analysis stages below, and every rejection comes with a diagnostic pointing at the offending line and column.
Rejects arithmetic on strings or on
checksig results and
comparisons across types. Keys must be on-curve literals in the
target's encoding, so a spender cannot supply their own key
(uncertain-sig) and no key is impossible to sign for
(impossible-key).
Ensures every declared witness item (especially signatures) is consumed exactly once, in stack order, on every path, and that the declarations match the paths. Prevents signature reuse and leftover stack items.
Checks literals against Bitcoin's consensus limits: numbers fit the 32-bit script range, byte strings fit the 520-byte push limit, and locktimes fit their BIP-65 and BIP-68 fields.
Enumerates every terminating path and asks whether it can succeed
with every checksig
forced to false, whether it can succeed at all, and whether it
ends in an explicit
return. Prevents
anyone-can-spend bugs and unspendable outputs.
The central security property:
no accepted spending path can succeed unless a
checksig on that path
returns true.
Rather than matching syntactic patterns, the analyzer turns each
path's success condition into a boolean formula, pins every
signature check to false, and asks whether the formula can still be
satisfied. Anything it cannot model exactly is treated as
attacker-controlled, so it errs toward rejecting a contract, never
toward accepting one.
This contract looks signature-gated, but its only path succeeds exactly when the signature check fails:
Because the check reasons about what each path computes rather than
how it is spelled, equivalent rewrites get the same verdict
(.. elides the
signature and key):
| Contract body | Verdict |
|---|---|
| return !checksig(..); | β UselessSig |
| return checksig(..) == false; | β UselessSig |
| return checksig(..) || flag; | β UselessSig |
| if checksig(..) { return false; } else { return true; } | β UselessSig |
| if flag { return true; } else { return checksig(..); } | β NoSigRequired |
| verify false; return checksig(..); | β DeadPath |
| return checksig(..) == true; | β Compiles |
| return !!checksig(..); | β Compiles |
The analysis is bounded at 20 attacker-controlled inputs per path; a
path above the bound is rejected rather than trusted. Keyless
spending paths, such as a bare hash lock or a bare timelock, are
outside the guarantee by design and rejected with
NoSigRequired.
Every diagnostic is reported as
Error at line L:C: Kind("message"). Names in italics are the defect classes of BSHunter (Zheng et
al., ICSE 2023), a study of defective scripts deployed on Bitcoin.
| Diagnostic | Stage | Rejected when |
|---|---|---|
| ParseError | Parser | The source does not match the grammar |
| InvalidOperation | Type checker |
An operand has the wrong type: arithmetic on a string or a
checksig result, comparing a number to a string,
a non-bool condition
|
| TypeMismatch | Type checker | A public key comes from the witness instead of a literal (uncertain-sig), or a signature is not a declared witness item |
| MalformedPubkey | Type checker | A key literal is not on secp256k1 or not in the target's encoding (impossible-key) |
| IntegerOverflow | Consensus literals | A number outside Β±2,147,483,647, a byte string over 520 bytes, or a locktime out of range |
|
UndefinedVariable DuplicateVariable |
Liveness & scope | A name is not declared on the path, or is declared twice in one declaration |
|
VariableConsumed UnusedVariable |
Liveness & scope | A witness item is used twice, or never used |
| InvalidConsumptionOrder | Liveness & scope | An item is used while one declared before it is still unused |
| DeclarationPathMismatch | Liveness & scope | The declarations do not match the spending paths in number or in their shared prefix |
|
NoReturn UnreachableCode |
Control flow |
A path has no return or an if has no
else; or a statement follows a
return or an if/else
|
| NoSigRequired | Semantic security | A path with no signature check can succeed (unbound-txid), or a path exceeds the analysis bound |
| UselessSig | Semantic security |
A path has a checksig but can succeed without a
valid signature (useless-sig)
|
| DeadPath | Semantic security | Every path is provably unsatisfiable (never-true) |
The Bithoven paper gives the language a formal syntax, big-step operational semantics and type system, and states two guarantees separately:
checksig outcome.
Since no accepted path succeeds with every
checksig false, some
checksig must have
returned true on chain.
The refinement theorem rests on an explicit trusted base, chiefly that Bitcoin Core implements the modeled fragment of Script. That assumption is tested rather than taken on faith:
The sixth BSHunter class, simple-key (a weak private key),
is invisible to any analysis of the contract. Every figure can be
reproduced with cargo test
and cargo bench; see
tests/README.md.
Scope
Script-level resource limits (201 opcodes, 10,000-byte scripts,
1,000 stack items, 20 keys per multisig) are not enforced, so a very
large contract can compile to an unspendable script. Taproot output
is checked against expected opcodes but not executed against
consensus, since libbitcoinconsensus has no taproot
verification flag.
Explore real-world Bitcoin smart contract patterns implemented in Bithoven. Each example demonstrates key concepts and can be tried live in the Web IDE.
π Suggested Learning Path
The most basic Bitcoin transaction - requires only a signature to spend.
π‘ Learns: Basic syntax, checksig operation
Use case: Simple wallet, standard payments
Spend coins by revealing a secret that matches a double-SHA256 hash, together with a signature.
π‘ Learns: Hash verification, secret reveals, why every path needs a signature
Use case: Atomic swaps, payment channels
Coins locked until a specific block height using absolute timelocks, then spendable with a signature.
π‘ Learns: Absolute timelocks (after), CLTV
Use case: Vesting schedules, delayed payments
Lightning Network standard combining hash locks and timelocks with if/else branches.
π‘ Learns: Conditional paths, combining hash+time locks, relative timelocks (older)
Use case: Lightning Network, payment channels
Trustless cross-chain trading with hash locks and timeout refunds.
π‘ Learns: Cross-chain patterns, double SHA256, refund mechanisms
Use case: Decentralized exchanges, cross-chain swaps
Taproot multi-signature authorization requiring both parties to sign.
π‘ Learns: Multisig syntax, Taproot vs SegWit differences
Use case: Joint accounts, shared custody
Marketplace contract with buyer, seller, and arbitrator. Multiple spending paths for different scenarios.
π‘ Learns: 3-party coordination, nested conditions, refund timeouts
Use case: P2P marketplaces, freelance platforms, real estate
Security wallet with delayed withdrawals and immediate cold storage recovery.
π‘ Learns: Hot/cold key hierarchy, security delays, emergency recovery
Use case: Corporate treasuries, high-value custody, exchange cold storage
Democratic governance requiring 2-of-3 approval with security timelock, or 3-of-3 emergency override.
π‘ Learns: Threshold signatures, governance delays, emergency overrides
Use case: DAO treasuries, board approvals, multi-stakeholder governance
Three-tier access: owner (anytime), heir (after 1000 blocks + secret), lawyers (after 10,000 blocks).
π‘ Learns: Cascading timelocks, secret-based access, fallback multisig
Use case: Estate planning, inheritance vaults, tiered recovery
Decentralized betting with cryptographic oracle commitment, multi-outcome resolution, and refund mechanism.
π‘ Learns: Oracle commitment schemes, multi-outcome branching, complex refund logic
Use case: Sports betting, price predictions, decentralized insurance
By Difficulty:
By Use Case:
A: Bithoven is a high-level, type-safe programming language for writing Bitcoin smart contracts. It provides imperative syntax (if/else, return) instead of low-level stack operations, and compiles to native Bitcoin Script.
A: No! That's the point. Bithoven abstracts away the complexity of Bitcoin Script's stack-based operations. You write readable code with familiar programming constructs, and the compiler handles the low-level details.
A: Miniscript (BIP-379) is a structured, composable subset of Bitcoin Script, best understood as an analyzable
assembly language and compiler target. Its grammar has no named witness bindings, arithmetic or imperative control flow, so it obtains
safety by excluding them. Bithoven is an imperative source language that admits those constructs and obtains comparable guarantees by
analysis instead. On 17 standard Miniscript policies, its output is within a median of one byte of rust-miniscript's.
A few Miniscript constructs have no Bithoven counterpart: pk_h, because keys must be literals;
or_d, whose OP_IFDUP idiom conflicts with linear
consumption; and thresh over arbitrary sub-policies, which Bithoven expresses only over keys
(multisig) or as a conjunction.
NoSigRequired?
A: By design, every spending path must require a valid signature. A bare hash lock lets anyone who sees the
preimage, for example in the mempool, claim the coins, and a bare timelock lets anyone spend once it expires. Add a signature
check to the path:
verify sha256(preimage) == "<hash>"; return checksig(sig, "<pubkey>");
A: v0.1.0 enforces rules that 0.0.x did not. The most common fixes are giving every if
an else, writing exactly one stack declaration per spending path, and using the key encoding the
target requires. See What's New in v0.1.0 for the full list.
older and after?
A: older N is a relative timelock (OP_CHECKSEQUENCEVERIFY) -
locks coins for N blocks after the UTXO is created.
after N is an absolute timelock (OP_CHECKLOCKTIMEVERIFY) -
locks coins until block height N. Use older for "wait X blocks from now", and after for "unlock at specific time".
A: No. Bitcoin Script (and therefore Bithoven) has amount blindness - scripts cannot read UTXO values or control output amounts. These require pre-signed transactions or off-chain coordination. Bithoven focuses on spending conditions, not transaction construction.
A: Bithoven v0.1.0 is in beta. The paper proves type safety and refinement for the language, and the compiler is differentially tested against Bitcoin Core, but it has not been independently audited. It also does not enforce Bitcoin's script-level resource limits, and its taproot output has not been executed against consensus (see Scope). Use it for research, prototyping, and learning. For production deployments, test on regtest or signet and conduct a security review.
A: Use the Web IDE
for quick experimentation. For thorough testing, compile your contract and use Bitcoin testing frameworks like
bitcoin-core regtest or btcd.
A: Check the GitHub repository for documentation, or open an issue if you encounter problems. For research questions, refer to the Bithoven paper.