How They Work
Vault Lifecycles
The CTV vault lifecycle is entirely deterministic. When you create the vault, you commit to every possible
future transaction upfront by hashing a template that locks in the outputs, amounts, and destinations. Funds
enter through a deposit to a P2WSH address whose script contains a CTV hash. To withdraw, you broadcast the
pre-signed unvault transaction that moves funds into a timelocked intermediate state. During the timelock
window, a watchtower can broadcast the pre-signed cold sweep to recover funds if the withdrawal looks
unauthorized. If no one intervenes, the hot wallet withdrawal completes after the delay. There is no partial
withdrawal — the entire vault balance moves as a single unit every time. This rigidity is what makes CTV
auditable: you can regenerate the full transaction tree from the initial parameters and verify that no
unauthorized paths exist. The total lifecycle costs 368 vB across three transactions (deposit 122 vB, trigger
94 vB, withdraw 152 vB). CTV uses two keys — a hot key for spending and a cold key for recovery — making it
the simplest key management model of the three designs.
Under the hood, CTV vaults use bare CTV scripts wrapped in P2WSH — no Taproot involved. The core opcode
OP_CHECKTEMPLATEVERIFY takes a 32-byte hash on the stack and verifies that the spending transaction matches
the committed template exactly: same outputs, same amounts, same sequence numbers. The vault script encodes
two spending paths. The first is the unvault path, which requires the CTV hash to match and the hot key
signature. The second is the cold sweep path, which requires only the cold key signature with no template
check, allowing emergency recovery to any destination. The unvault output itself contains another CTV-locked
script with its own two paths: a timelocked hot withdrawal (requiring CSV delay plus hot key) and a cold
sweep. This creates a hash-chain of pre-committed transactions where every state transition is fully specified
at vault creation time. The P2WSH approach means witness scripts are revealed only at spend time, but the
entire tree structure is knowable from the root hash. No script path is hidden — there is no Taproot key-path
shortcut, no internal key optimization, just bare script verification against committed templates.
The safety model of a CTV vault emerges from its nesting structure. Each transaction is built
backwards — the innermost withdrawal is constructed first, then wrapped by the trigger, then
wrapped by the vault deposit. The entire execution path is cryptographically committed before any
funds enter the system. The ephemeral signing key used during construction is destroyed
immediately after, making the commitment permanent and irreversible. During execution, the flow
reverses inward: the outer vault layer is spent first, then the middle trigger layer after a time
delay, and finally the inner withdrawal completes. Recovery exists as a bypass that can interrupt
this inward flow at any point, sweeping funds to cold storage. The rigidity is what makes this a
safety property rather than just an implementation detail — no transaction can deviate from the
pre-committed template, which means no fee adjustment, no partial withdrawal, and no destination
change after vault creation. Every lock between layers is enforced by the CTV hash matching the
exact transaction that follows. The safety guarantee is structural: if the hash chain is valid at
creation time, no future execution can violate it.
CCV vaults store state inside Taproot output tweaks, which lets them do something CTV cannot: partial
withdrawals. When you deposit into a CCV vault, your funds land in a P2TR output whose internal key is a NUMS
point (nothing-up-my-sleeve, meaning no one can spend via the key path). The spending rules live entirely in
Taproot script leaves. To trigger a withdrawal, you provide a signature from the trigger key and the CCV
opcode verifies that the spending transaction creates valid successor outputs — a timelocked withdrawal output
and optionally a change output that returns remaining funds to a new vault with the same contract rules. This
change-back mechanism is the revault: you can withdraw 0.5 BTC from a 10 BTC vault and the other 9.5 BTC stays
vaulted under identical protections. Recovery is keyless — anyone who knows the vault address can broadcast
the recovery transaction, which sends all funds to the pre-committed recovery address. The measured lifecycle
costs 565 vB across three transactions (deposit 300 vB, trigger-and-revault 154 vB, withdraw 111 vB),
reflecting CCV's ability to trigger a partial withdrawal and return the remainder to a new vault in a single
transaction. CCV uses only one key for the trigger, with recovery requiring no key at all.
CCV's internal machinery relies on OP_CHECKCONTRACTVERIFY to enforce state transitions across transactions.
Each vault output is a P2TR address with a NUMS internal key (specifically the generator point negated so no
discrete log is known). The contract rules live in Taproot leaf scripts. When a trigger transaction spends the
vault, CCV checks that the outputs conform to the contract: the withdrawal amount goes to a timelocked script,
and any change goes back to a new vault output with the same Taptree structure. The mode flags in CCV control
what gets verified — mode 0 checks the output's scriptPubKey matches a committed template, mode 1 verifies
amounts, mode 2 checks both. These mode bytes are critical because any value outside [0, 1, 2] causes the
interpreter to execute OP_SUCCESS, which makes the script trivially spendable by anyone. The minivault variant
uses CCV alone without CTV, while the full vault combines both opcodes. State is carried forward through
Taproot tweaks: the contract's Taptree is reattached to each successor output, ensuring the same spending
rules apply to change outputs across an unlimited number of revault cycles.
At the opcode level, OP_CHECKCONTRACTVERIFY performs bilateral introspection — it sits between
the input and output sides of a transaction and verifies that the output's structure mirrors the
input's contract rules. When a vault UTXO is spent, CCV inspects the spending transaction's
outputs and checks three things depending on its mode flag: that the output scriptPubKey matches
the committed template (mode 0), that the output amount satisfies the contract (mode 1), or both
(mode 2). The input taptree — with its trigger and recovery leaves — must be carried forward to
the output taptree intact, preserving the contract across state transitions. State data shifts
through CCV as the input's internal key tweak is replaced by the output's tweak, carrying the
vault balance and withdrawal parameters forward. The mode byte is the critical execution detail:
CCV treats any value outside [0, 1, 2] as OP_SUCCESS, which makes the entire script trivially
true — no signature needed, no covenant check, no constraints. This is not a bug but an
intentional forward-compatibility mechanism. At the opcode level, it means a single byte controls
whether CCV acts as a verification gate or an open door.
OP_VAULT uses a dedicated opcode pair — OP_VAULT and OP_VAULT_RECOVER — purpose-built for vault contracts,
unlike CTV and CCV which are general-purpose covenant opcodes adapted for vault use. The lifecycle begins with
a deposit to a P2TR address whose internal key is the recovery pubkey. The trigger key holder starts a
withdrawal by broadcasting a trigger transaction that invokes OP_VAULT, which enforces a timelock delay and
commits to a specific withdrawal destination. During the delay, a watchtower can broadcast an authorized
recovery transaction using OP_VAULT_RECOVER, which requires the recoveryauth key signature. If no recovery
happens, the withdrawal completes after the timelock expires. OP_VAULT's distinctive feature is authorized
recovery: unlike CCV where anyone can trigger recovery, here only the recoveryauth key holder can do it. This
prevents griefing attacks where random observers force funds back to cold storage. The cost is a three-key
model — trigger, recoveryauth, and recovery address — which is the most complex key management of the three
designs. The lifecycle costs 567 vB (deposit 154 vB, trigger 292 vB, withdraw 121 vB), roughly 36% more than
CCV, largely due to the 2-input fee wallet pattern that adds 80–90 vB to trigger and recovery transactions.
OP_VAULT's internal structure uses P2TR with the recovery pubkey as the Taproot internal key. This is a
deliberate design choice: if all three keys are available and cooperating, a key-path spend (using the
recovery pubkey) can bypass the script entirely. The Taptree contains leaf scripts for OP_VAULT (trigger path)
and OP_VAULT_RECOVER (recovery path). When OP_VAULT executes, it verifies that the spending transaction
creates an output with a specific structure — a timelocked script committing to the declared withdrawal
destination. The opcode enforces the delay parameter and output matching in consensus, meaning a miner cannot
forge a valid trigger. OP_VAULT_RECOVER checks the recoveryauth signature and ensures funds move to the
committed recovery address. The recovery address is baked into the vault at creation and is immutable — even
with both the trigger key and recoveryauth key compromised, an attacker cannot redirect recovery funds. This
is why dual-key compromise (TM6) results in denial-of-service rather than theft: the attacker can trigger and
then recover in a loop, but funds always land at the legitimate recovery address. The fee wallet pattern uses
a separate UTXO to pay transaction fees, avoiding the need to deduct fees from the vault amount, but adding an
extra input to every transaction.
At the opcode level, OP_VAULT performs consensus-enforced taproot tree surgery — it directly
modifies the spending UTXO's taptree structure within the transaction validation rules. The vault
UTXO is a taproot output with two script leaves: a vault-spend leaf (containing the OP_VAULT
opcode) and a recovery leaf. When the vault-spend leaf is executed, OP_VAULT reads the trigger
parameters from the witness stack and does three things in a single validation pass: it prunes
the vault-spend leaf from the taptree, grafts a new CTV-locked leaf in its place that commits to
the declared withdrawal destination and timelock, and copies the recovery leaf through to the
output taptree unchanged. The opcode validates this surgery by recomputing the expected output
scriptPubKey from these components and checking it against the actual transaction output — if the
recomputed taptree doesn't match, the transaction is invalid at the consensus level. The three
keys occupy distinct execution tiers: the trigger key sits in the vault-spend leaf and is checked
by script during the spend path, the recovery authorization key gates the recovery leaf and must
sign to exercise that path, and the recovery destination is baked into the taproot internal key
at construction time — not in any script leaf, but in the key-path tweak itself, which means no
script execution can alter where recovery funds land. This separation is enforced by the opcode's
tree surgery: because OP_VAULT only replaces its own leaf while preserving everything else, the
recovery path and its embedded destination survive every state transition untouched.
The CAT+CSFS vault takes a fundamentally different approach from the other three designs: instead of a
purpose-built opcode, it composes a vault from two general-purpose primitives — OP_CAT (BIP 347) for
concatenating stack items and OP_CHECKSIGFROMSTACK (BIP 348) for verifying signatures against
arbitrary messages. The lifecycle begins with a deposit to a P2TR address whose internal key is a NUMS
point (unspendable key path), with two Taproot leaf scripts: a trigger leaf containing the CSFS+CAT
introspection logic, and a recover leaf with a simple cold key OP_CHECKSIG. To trigger a withdrawal,
the hot key holder broadcasts a transaction that passes dual Schnorr verification — the same signature
is checked against both the real transaction sighash (via OP_CHECKSIGVERIFY) and a witness-provided
sighash preimage (via OP_CHECKSIGFROMSTACK). If both pass, the preimage is proven authentic, and the
script verifies that the output matches the expected vault-loop address. Funds then sit in the
vault-loop state, protected by a CSV timelock. After the delay, the same dual-verification mechanism
enforces that funds go to the pre-committed destination — which is locked at vault creation time as a
sha_single_output constant embedded in the script. The destination cannot be changed without recovering
to cold storage and creating a new vault. Recovery uses the cold key via a simple OP_CHECKSIG in the
recover leaf — no introspection needed, no constraints on where funds go. The total lifecycle costs
553 vB across three transactions (deposit 122 vB, trigger 221 vB, withdraw 210 vB). Like CTV, it uses two keys
(hot + cold) and does not support partial withdrawals or revaulting.
The dual Schnorr verification at the heart of CAT+CSFS is an elegant trick: the witness provides a
decomposed sighash preimage (prefix, sha_single_output, suffix) and a copy of the signature. The
script first runs OP_CHECKSIGVERIFY to verify the signature against the actual transaction sighash —
this is normal Bitcoin signature verification. Then OP_CAT concatenates the witness-provided preimage
pieces into a single message on the stack. OP_CHECKSIGFROMSTACK verifies the same signature against
this assembled message. Because a Schnorr signature can only be valid for one message under the same
key (discrete log uniqueness), the assembled preimage must be the real sighash. The script then
extracts the sha_single_output field from the proven preimage and compares it to a hash constant
embedded in the script at vault creation. If they match, the transaction output is guaranteed to pay
the pre-committed destination. The sighash type SIGHASH_SINGLE|ANYONECANPAY (0x83) covers only one
input and its corresponding output, which means additional fee-paying inputs can be attached without
invalidating the covenant signature — a cleaner fee management approach than CTV's CPFP anchors. The
tradeoff is that the destination is permanently fixed: the embedded hash cannot change without
destroying and re-creating the vault.
At the opcode level, the CAT+CSFS vault script is a verification pipeline. The Taproot output has a
NUMS internal key (preventing key-path spends) and a two-leaf taptree. The trigger/withdraw leaf
contains the pipeline: OP_CHECKSIGVERIFY verifies the signature against the real transaction, two
OP_CAT operations concatenate the preimage fragments (prefix + sha_single_output + suffix), then
OP_CHECKSIGFROMSTACK verifies the same signature against the assembled preimage, and finally
OP_EQUALVERIFY confirms the output hash matches the embedded constant. The recover leaf is deliberately
simple — just cold_pk OP_CHECKSIG with no introspection, no constraints, and no output restrictions.
This asymmetry is the design's security model: the trigger path is computationally constrained
(outputs must match embedded hashes), while the recovery path is unconstrained (cold key can sweep
anywhere). The SIGHASH_SINGLE|ANYONECANPAY flag is critical at this level — it means the sighash
preimage only commits to one input and one output, allowing fee-paying inputs to be added freely. This
is why CAT+CSFS vaults don't need anchor outputs or CPFP: the covenant signature remains valid
regardless of what other inputs or outputs exist in the transaction, as long as the constrained output
matches the embedded hash.
The timelock race is the fundamental security mechanism shared by all four vault designs. When an
unauthorized party triggers a withdrawal — whether through key theft, malware, or coercion — a clock starts.
The withdrawal is timelocked: it cannot complete until a specified number of blocks have been mined. During
this window, a watchtower (software monitoring the blockchain for vault-related transactions) detects the
unauthorized trigger and broadcasts a recovery transaction to sweep funds back to cold storage. The attacker's
only option is to try to prevent the recovery transaction from confirming before the timelock expires. In CTV,
the attacker can attempt fee pinning — chaining descendant transactions off the unvault output to block CPFP
fee bumping, exploiting the 25-transaction descendant limit. In CCV and OP_VAULT, the attacker can attempt
watchtower exhaustion — splitting the vault into many small UTXOs and triggering them all, forcing the
watchtower to pay recovery fees for each one. The race outcome depends on the fee environment: at low fees,
pinning is cheap and CTV is most vulnerable; at high fees, recovery is expensive and CCV/OP_VAULT watchtowers
can be bled dry. This fee-dependent inversion means no single vault design wins the race under all conditions.
Threat Models
Where the Vulnerabilities Are
CRITICAL Fee Pinning (TM1)
Fee pinning exploits CTV's transaction structure to block emergency recovery. When the unvault transaction
broadcasts, its output can be spent by anyone who knows the pre-committed template — because CTV scripts are
public once revealed. An attacker chains 25 low-fee descendant transactions off the unvault output, hitting
Bitcoin Core's descendant limit (MAX_DESCENDANT_COUNT). The cold sweep transaction, which needs to spend that
same output via CPFP, is rejected by mempools because the descendant chain is full. The watchtower cannot bump
fees, cannot replace the junk transactions (they don't signal RBF), and cannot get the recovery transaction
into any block. When the timelock expires, the attacker's hot withdrawal confirms and the funds are gone. This
attack costs almost nothing — the 25 junk transactions can pay minimum relay fees. It is unique to CTV because
its P2WSH structure exposes the unvault output to third-party spending, and the pre-committed transaction
template cannot include anchor outputs or other fee-management mechanisms added after vault creation. CCV and
OP_VAULT are immune because their Taproot structures don't expose intermediate outputs to third-party
descendant chaining.
Measured on Regtest
Descendant Chain
25 txs
2,750 vB total (25 × 110)
Capital Needed
~550 sats
Anchor output dust
Defender Blocked
133 vB
Cold sweep rejected — desc limit full
Attack Cost
< 0.05%
Of vault balance at any fee rate
CTV only — P2WSH exposes the unvault output to third-party descendant chaining. CCV and OP_VAULT are immune (Taproot key-path spend).
CRITICAL CCV Mode Bypass (TM8)
CCV mode bypass is the most consequential developer footgun we documented. The CCV opcode accepts a mode byte that
controls verification behavior: mode 0 checks scriptPubKey, mode 1 checks amount, mode 2 checks both. Any mode
value outside this set — 3, 4, 255, anything — triggers OP_SUCCESS, a consensus rule designed for future
soft-fork extensibility. OP_SUCCESS makes the entire script evaluate to true unconditionally: no signature
needed, no covenant check, no constraints at all. We built a structurally complete vault contract with one
leaf script accidentally using mode=3 and drained it with a 110 vB transaction containing no signature. The
funds moved to an arbitrary destination with zero authentication. This is not a bug in the CCV specification —
it is the intended behavior for forward compatibility. But it means a single byte error in vault construction
creates a script that looks correct, passes every static analysis check, deploys successfully, accepts
deposits normally, and then allows anyone who notices the mode flag to steal everything. The attack surface
exists only in CCV because CTV has no mode flags and OP_VAULT uses dedicated opcodes with no extensibility
trap.
Bypass Modes Tested
| Mode |
Behavior |
Result |
| 0, 1, 2 |
Normal CCV check |
Validates correctly |
| 3 |
OP_SUCCESS (undefined) |
Theft — 110 vB |
| 4 |
OP_SUCCESS (undefined) |
Theft — 110 vB |
| 7 |
OP_SUCCESS (undefined) |
Theft — 110 vB |
| 128 |
OP_SUCCESS (undefined) |
Theft — 110 vB |
| 255 |
OP_SUCCESS (undefined) |
Theft — 110 vB |
5/5 undefined modes accepted. Zero signatures required. Funds sent to arbitrary address. Control vault (mode 0) correctly rejected the same spend.
SEVERE Trigger Key Theft (TM3 + TM5)
Trigger key theft is the canonical vault attack: an adversary compromises the hot key used to initiate
withdrawals and attempts to steal funds before the watchtower can react. In all four vault designs, the
attack follows the same pattern — the attacker triggers an unauthorized withdrawal, the timelock delay begins,
and the watchtower races to broadcast recovery. The difference is recovery cost. CTV recovery uses a
pre-signed cold sweep at 133 vB. CCV recovery is the cheapest at 122 vB because keyless recovery requires no
signature, just knowledge of the vault address. OP_VAULT recovery is the most expensive at 246 vB because the
2-input fee wallet pattern adds overhead and the recoveryauth signature adds witness data. Across all three
designs, the watchtower wins this race as long as it is online and funded — the timelock gives it blocks, not
seconds, to respond. The real danger is trigger key theft combined with watchtower failure, which escalates to
fund theft in CTV (attacker completes the hot withdrawal) and CCV (attacker completes withdrawal to their
chosen destination). In OP_VAULT, even with watchtower failure, the attacker cannot redirect recovery funds
because the recovery address is immutable.
Recovery Cost Comparison
|
CTV |
CCV |
OP_VAULT |
CAT+CSFS |
| Recovery tx |
133 vB |
122 vB |
246 vB |
125 vB |
| Mechanism |
Pre-signed cold sweep |
Keyless (no sig) |
2-input + auth sig |
Cold key CHECKSIG |
| Cost vs attacker |
1.41× |
1.26× |
1.19× |
~0.57× |
Watchtower wins the race in all cases — timelocks give blocks, not seconds, to respond. The cost difference matters in sustained multi-round attacks (see TM4). CAT+CSFS recovery is cheap because the recover leaf is a simple CHECKSIG — no introspection overhead.
SEVERE Recovery Griefing (TM2)
Recovery griefing is the direct consequence of CCV's keyless recovery design. Since anyone can trigger
recovery without any key or signature — they just need to know the vault's address and construct a valid
recovery transaction — a griefer can watch for trigger transactions and immediately broadcast recovery,
sending funds back to cold storage before the legitimate withdrawal completes. No funds are stolen, but the
vault owner is permanently denied access to their hot wallet. Every withdrawal attempt gets front-run by the
griefer, creating an infinite denial-of-service loop. The griefer pays only the recovery transaction fee (122
vB × fee rate), which is cheap compared to the damage inflicted. CTV has moderate griefing exposure because
the cold sweep requires a cold key signature — only someone with the cold key can grief. OP_VAULT has the best
griefing resistance because recovery requires the recoveryauth key signature, making anonymous griefing
impossible. The structural tradeoff is that CCV's keyless recovery is simultaneously its greatest strength
(funds recoverable even if all keys are lost) and its greatest weakness (anyone can trigger recovery). You
cannot have both open recovery access and griefing resistance — they are fundamentally opposed design goals.
Cost Asymmetry per Round
|
CTV |
CCV |
OP_VAULT |
CAT+CSFS |
| Trigger tx |
94 vB |
154 vB |
292 vB |
221 vB |
| Recovery tx |
133 vB |
122 vB |
246 vB |
125 vB |
| Asymmetry |
0.71× |
1.26× |
1.19× |
1.77× |
| Grief barrier |
Cold key required |
None (keyless) |
Auth key required |
Cold key required |
Asymmetry > 1× means the defender pays more per round than the griefer. CCV is worst: zero barrier (keyless recovery) + unfavorable cost ratio. OP_VAULT is best: requires auth key, making anonymous griefing impossible. CAT+CSFS is similar to CTV: cold key required for recovery, favorable cost ratio for the defender.
SEVERE Watchtower Exhaustion (TM4 + TM7)
Watchtower exhaustion turns CCV and OP_VAULT's partial withdrawal feature into an attack vector. The attacker,
holding the trigger key, splits the vault into hundreds or thousands of small UTXOs using repeated partial
withdrawals, each creating a new independent vault. Then the attacker triggers withdrawals on all of them
simultaneously. The watchtower must now broadcast a separate recovery transaction for every single UTXO,
paying fees on each one. At 122 vB per recovery for CCV and 246 vB per recovery for OP_VAULT, the watchtower's
fee budget is quickly exhausted. Once the budget runs out, remaining triggered UTXOs complete their
unauthorized withdrawals uncontested. OP_VAULT fails roughly twice as fast as CCV because its recovery
transactions cost twice as much. CTV is completely immune to this attack because it does not support partial
withdrawals — there is no way to split a CTV vault into smaller pieces. The attacker cannot create the initial
UTXO fragmentation that the attack requires. This is the clearest example of the fee-dependent security
inversion: at low fees, the splitting phase is cheap and the attack is viable against CCV and OP_VAULT. At
high fees, splitting is expensive but recovery is also expensive, creating a complex cost tradeoff.
Fee Sensitivity — Splits to Exhaust 0.5 BTC Vault
1 sat/vB
~410,000 splits needed
Infeasible
10 sat/vB
~41,000 splits needed
Hard
50 sat/vB
~8,200 splits needed
Feasible
300 sat/vB
~1,400 splits needed
Viable
500 sat/vB
~820 splits needed
Easy
Per Split
162 vB
Trigger + revault (constant)
Per Recovery (CCV)
122 vB
Keyless, per-UTXO
Per Recovery (OPV)
246 vB
Fails 2× faster than CCV
50 actual splits executed on regtest — vsize constant across all rounds. Batched recovery saves 40–45% but cannot prevent exhaustion at sustained high fees. CTV is immune (no partial withdrawals).
HIGH Dual-Key
Compromise (TM6)
Dual-key compromise examines what happens when an attacker obtains both the trigger key and the recoveryauth
key in an OP_VAULT system. This sounds like it should be game over — and in CTV it is, because holding both
the hot key and cold key gives the attacker complete control. But OP_VAULT's design produces a surprising
result: denial-of-service, not theft. The attacker can trigger a withdrawal and then immediately use the
recoveryauth key to invoke authorized recovery, sending funds back to the recovery address. They can repeat
this loop indefinitely, preventing the legitimate owner from ever completing a withdrawal. However, they
cannot redirect funds because the recovery address was set at vault creation and is immutable — it is embedded
in the Taproot internal key structure and cannot be changed by anyone, including someone holding all three
keys. The attacker is stuck in a trigger-recover loop where funds always return to the legitimate recovery
address. To actually steal funds, the attacker would need to compromise the recovery address itself (a
separate key or multisig), making this effectively a three-key attack threshold. CCV's single-key model means
this specific scenario does not apply — there is only one trigger key and recovery is keyless.
Simplicity (on Elements) constrains both spending paths via jet::outputs_hash(), so
dual-key compromise produces DoS only — matching OP_VAULT's outcome through a different mechanism.
Compromise Outcomes
|
CTV |
CCV |
OP_VAULT |
CAT+CSFS |
| Keys compromised |
Hot + cold |
N/A (single key) |
Trigger + auth |
Hot + cold |
| Outcome |
Fund theft |
— |
DoS only |
Fund theft |
| Why |
Full control of both paths |
No second key exists |
Recovery addr is immutable |
Cold key bypasses all constraints |
OP_VAULT's immutable recovery address (embedded in the taproot internal key tweak) means dual-key compromise produces denial-of-service, not theft. CTV and CAT+CSFS both allow fund theft when both keys are compromised — the cold key recovery path has no output constraints. CAT+CSFS cold key theft directly enables an attacker to sweep vault funds since the recover leaf is an unconstrained CHECKSIG.
HIGH Cold Key Recovery Theft (TM11)
This is the most severe CAT+CSFS vulnerability. The recovery leaf uses a simple cold_pk OP_CHECKSIG
with SIGHASH_DEFAULT — there is no CAT, no CSFS, no output introspection of any kind. A compromised cold key
can construct a recovery transaction sending funds to any arbitrary address, immediately, with no timelock delay
and no output constraints. Compare this to the other three designs: CCV recovery is keyless and output-constrained
(funds always go to the pre-committed recovery address), OP_VAULT recovery requires the recoveryauth key and is
output-constrained (funds always go to the recovery SPK hash), and CTV recovery uses a pre-committed CTV template
that locks the destination at vault creation. CAT+CSFS ranks last in recovery security — cold key compromise means
immediate, unrestricted, total theft. The 125 vB recovery transaction looks identical to a legitimate cold sweep
on-chain; there is no way for a watchtower to distinguish theft from legitimate recovery. Poelstra's recursive
reset model (from "CAT and Schnorr Tricks II") would upgrade this to liveness denial only, matching OP_VAULT's
security profile, but is not implemented in the current design. Simplicity constrains recovery outputs via
jet::outputs_hash(), making cold key compromise recovery-address-locked — matching CCV and
OP_VAULT, not CAT+CSFS.
Recovery Security Ranking
| Covenant |
Recovery Model |
Cold Key Compromise |
Result |
| CCV |
Keyless, output-constrained |
N/A (no key) |
Safest |
| OP_VAULT |
Keyed, output-constrained |
Grief only (recovery addr immutable) |
Grief only |
| CTV |
CTV-constrained template |
Wallet key → theft of recovered funds |
Moderate |
| CAT+CSFS |
Unconstrained OP_CHECKSIG |
Immediate total theft — any address |
Critical |
Recovery vsize
125 vB
From vault or vault-loop
Output constraint
None
cold_pk CHECKSIG only
Timelock
None
No CSV on recovery leaf
Distinguishable
No
Theft looks identical to legitimate recovery
CAT+CSFS only. Recovery leaf script: cold_pk OP_CHECKSIG. No CAT, no CSFS, no output introspection. Cold key holder has unconstrained control over recovery destination.
IMPOSSIBLE Hot Key Theft — Output Redirection (TM9)
CAT+CSFS's strongest defensive property: even with a fully compromised hot key, the attacker cannot redirect
vault funds to an attacker-controlled address. The trigger leaf embeds sha_single_output as a script
constant — the SHA256 hash of the expected output. The dual CSFS+CHECKSIGVERIFY verification creates an
inseparable binding: CHECKSIGVERIFY checks the signature against the real transaction sighash (which includes the
actual output), while CSFS checks the same signature against a preimage assembled from witness data that includes
the embedded sha_single_output. If the attacker changes the output, the real sighash changes but the
CSFS preimage doesn't — the same signature cannot satisfy both checks. This is enforced at the consensus level
(not relay policy). The attacker's only option is to trigger the vault to the pre-embedded vault-loop address
repeatedly — pure griefing with no theft path. This makes CAT+CSFS the only vault design where the hot key
holder cannot influence the withdrawal destination at trigger time. CTV commits to the full transaction template
(including destination) but through a different mechanism; CCV and OP_VAULT both allow the trigger key holder to
choose the destination at trigger time.
Output Redirection Attempts
| Attack |
Method |
Result |
| Redirect output |
Change output to attacker address |
REJECTED (consensus) |
| Extra output |
Add output via SIGHASH_SINGLE |
Accepted but funded by extra inputs |
| Legitimate trigger |
Trigger to embedded vault-loop |
Accepted — griefing only |
Trigger vsize
221 vB
Normal trigger transaction
Mutated trigger
REJECTED
Consensus-level enforcement
Theft path
None
Hot key → griefing only
CAT+CSFS only. Dual CSFS+CHECKSIGVERIFY creates an inseparable binding to the pre-embedded destination. The hot key can trigger repeatedly (griefing) but cannot change where funds go.
IMPOSSIBLE Witness Manipulation — Preimage Forgery (TM10)
Tests whether an attacker can tamper with the sighash preimage components to bypass the CAT+CSFS covenant. The
preimage is split into three witness items: prefix (untrusted, ~94 bytes), sha_single_output (trusted,
32 bytes, script-embedded), and suffix (untrusted, ~37 bytes). CAT concatenates them, CSFS verifies the signature
against the assembled hash, and CHECKSIGVERIFY verifies the same signature against the real transaction. Three
independent tampering vectors were tested: modifying the nVersion byte in the prefix (0x02 → 0x03), modifying
codesep_pos in the suffix (0xFFFFFFFF → 0x00000000), and changing the hash type from SIGHASH_SINGLE|ANYONECANPAY
to SIGHASH_ALL. All three are rejected at the consensus level — any modification to prefix or suffix causes the
CSFS-computed sighash to diverge from the real sighash, and the same signature cannot satisfy both checks. The
total preimage is 227 bytes, well within the 520-byte OP_CAT stack element limit. For any transaction T with
embedded hash S, there exists exactly one valid (prefix, suffix) pair — finding a second requires breaking SHA256.
Tampering Vectors Tested
| Vector |
Modification |
Result |
| Prefix |
nVersion 0x02 → 0x03 |
REJECTED (consensus) |
| Suffix |
codesep_pos 0xFFFFFFFF → 0x00000000 |
REJECTED (consensus) |
| Hash type |
SIGHASH_SINGLE|ACP → SIGHASH_ALL |
REJECTED (consensus) |
Prefix
~94 B
Witness-provided (untrusted)
sha_single_output
32 B
Script-embedded (trusted)
Suffix
~37 B
Witness-provided (untrusted)
Total / Limit
227 / 520 B
OP_CAT stack limit ✓
All tampering vectors rejected at consensus level. For any transaction T with embedded hash S, exactly one valid (prefix, suffix) pair exists. Finding a second requires breaking SHA256.
Design Tradeoffs
Structural Differences That Matter
DESIGN Address Reuse — Single-Use vs Multi-Deposit
What happens when a user accidentally sends a second deposit to the same vault address? CTV and CAT+CSFS have
single-use addresses — the second deposit is permanently stuck (CTV because the CTV hash commits to exact output
amounts at creation time; CAT+CSFS because the CSFS signature commits to the deposit amount). CCV and OP_VAULT
handle multiple deposits safely — each deposit creates an independent contract/UTXO that can be spent on its own
terms. This is not an adversarial attack but a user-error footgun. For customer-facing systems (e-commerce, deposit
addresses), CTV and CAT+CSFS are disqualified unless wallet software strictly enforces single-use address
discipline. With CAT+CSFS there is a nuance: if the signers cooperate, a new signature can be computed for the
new amount (the commitment is in the signature at spend-time, not in the script at creation-time). Without signer
cooperation, funds are stuck just like CTV.
Second Deposit Behavior
| Covenant |
Second Deposit |
Unvault Attempt |
Result |
| CTV |
Same scriptPubKey |
bad-txns-in-belowout |
Funds permanently lost |
| CCV |
Independent contract |
Both withdraw independently |
Both safe |
| OP_VAULT |
Independent UTXO |
Both withdraw independently |
Both safe |
| CAT+CSFS |
Same P2TR address |
Signature amount mismatch |
Stuck (recoverable with cooperation) |
CTV commitment is at creation time (script-level). CAT+CSFS commitment is at spend time (signature-level) — more flexible but still single-use in practice. CCV and OP_VAULT are inherently multi-deposit safe.
CAPABILITY Revault Amplification — Partial Withdrawal Chaining
CCV supports native partial withdrawals via its trigger_and_revault clause — a single trigger splits
the vault into an unvaulting output (for withdrawal) and a new vault output (holding the remainder). This can be
chained repeatedly without recreating the vault. OP_VAULT similarly supports partial withdrawal through
OP_VAULT's built-in excess handling. CTV and CAT+CSFS have no native partial withdrawal — every withdrawal
requires destroying the entire vault and recreating with new parameters, paying the full lifecycle cost each time.
For N partial withdrawals from a 0.5 BTC vault, CCV's cumulative cost scales sub-linearly (the revault output
shrinks with each step), while CTV/CAT+CSFS scale linearly at N × full_cycle_cost. This capability gap is also
relevant to griefing economics: an attacker triggering CCV/OP_VAULT vaults can use partial triggers to split UTXOs,
multiplying the watchtower's recovery burden (see TM4/TM7).
Partial Withdrawal Cost Model (5M sat steps from 50M sat vault)
CCV per step
~162 vB
trigger_and_revault (sub-linear)
CTV per step
~368 vB
Full cycle: destroy + recreate
CAT+CSFS per step
~553 vB
Full cycle: destroy + recreate
10-step savings (CCV)
~56%
vs CTV's 10 × full cycle
CCV and OP_VAULT support native partial withdrawal. CTV and CAT+CSFS require full vault destruction and recreation for each partial withdrawal. 10 actual revault steps executed on regtest — vsize constant per step.
CAPABILITY Multi-Input Batching — Trigger Scaling
CCV and OP_VAULT can batch multiple vault UTXOs into a single trigger transaction, reducing per-vault marginal
cost as N increases. CTV and CAT+CSFS cannot batch — each vault UTXO requires its own trigger transaction.
At N=50 inputs, CCV reaches a per-vault cost of ~101 vB (34% savings vs N=1), while CTV and CAT+CSFS remain
constant regardless of count. OP_VAULT batching is also sub-linear with ~94 vB marginal cost per
additional input vs CCV's ~106 vB). CCV has a DEDUCT footgun: if multiple inputs use DEDUCT mode pointing to
the same revault output, the accounting creates contradictory constraints and the transaction is rejected.
Safe patterns include single-input DEDUCT, mixed trigger+trigger_and_revault, or separate revault outputs per
DEDUCT input. The standardness ceiling (~400k weight units) projects a maximum of ~1,600 vaults per batched
CCV trigger transaction; the block weight ceiling (4M WU) allows ~16,000 in theory.
CCV DEDUCT Footgun
| Pattern |
Config |
Result |
| Safe: mixed |
1× DEDUCT + 1× preserve |
ACCEPTED |
| Safe: all-trigger |
All preserve (no revault) |
ACCEPTED |
| Unsafe: dual DEDUCT |
2× DEDUCT → same revault output |
REJECTED (accounting) |
CCV-specific footgun. Multiple DEDUCT inputs pointing to the same revault output create contradictory amount constraints. Safe coordinator patterns exist but require careful implementation.
Beyond Bitcoin
Simplicity on Elements
Simplicity is a typed functional language for blockchain covenants, deployed on Elements/Liquid — a
federated sidechain. It is not a Bitcoin opcode proposal. We include it as a reference implementation
to show how a fundamentally different programming model and trust architecture affect vault design.
The measurements below are from Elements regtest and are not directly comparable to Bitcoin regtest
due to differences in fee markets, witness structure, and consensus model.
Simplicity Elements / Liquid
The Typed One
Pure functional programs compiled to a directed acyclic graph (DAG). Covenant enforcement via native
jet::outputs_hash() introspection — both the trigger path and recovery path are
output-constrained. Runs on a federated sidechain with explicit fee outputs, eliminating fee pinning
structurally rather than through script design.
The Simplicity vault lifecycle operates on Elements regtest. Funds enter the vault through a deposit
to a P2TR output whose spending conditions are defined by a Simplicity program — a typed functional
DAG rather than a stack-based Bitcoin Script. The vault program embeds two BIP-340 Schnorr public keys
(hot and cold, derived from separate BIP-39 mnemonics) and two pre-computed output hashes as compile-time
parameters. To trigger a withdrawal, the hot key holder signs a transaction that is validated by the
Simplicity program: jet::bip_0340_verify() checks the signature, and
jet::outputs_hash() verifies that the spending transaction's outputs match the pre-committed
hash. After a CSV timelock delay enforced by jet::check_lock_distance(), the withdrawal
completes to the pre-committed destination. Recovery uses the cold key but is also output-constrained —
unlike CAT+CSFS where the recovery leaf is an unconstrained OP_CHECKSIG, Simplicity's
recovery path runs the same jet::outputs_hash() check, ensuring funds can only move to the
pre-committed recovery address. The total lifecycle costs 865 vB across three transactions (deposit
269 vB, trigger 298 vB, withdraw 298 vB). The larger witness size reflects Simplicity's bit-oriented
DAG encoding, which includes the pruned program, Merkle proof, and serialized witness data. Elements
uses explicit fee outputs rather than the implicit fee model (input sum minus output sum) used in
Bitcoin, which eliminates descendant-chain fee pinning at the protocol level.
The covenant enforcement mechanism in Simplicity centers on jet::outputs_hash() — a
native jet (optimized built-in function) that computes the Merkle hash of the spending transaction's
outputs at consensus time. The vault program is a DAG with two branches selected by the witness
spend-path: Left for the trigger (hot key) and Right for recovery (cold key). Both branches execute
the same verification pipeline: jet::bip_0340_verify() checks the Schnorr signature
against the appropriate public key, then jet::outputs_hash() computes the actual output
hash and compares it against the pre-committed parameter (param::TRIGGER_OUTPUTS_HASH or
param::RECOVER_OUTPUTS_HASH). If the hashes do not match, the program evaluates to false
and the transaction is invalid. This is the key structural difference from CAT+CSFS: in a CAT+CSFS
vault, the trigger leaf uses dual CSFS+CHECKSIG verification to constrain outputs, but the recovery
leaf is a bare cold_pk OP_CHECKSIG with no introspection — a compromised cold key can
sweep funds to any address. In Simplicity, both paths are output-constrained by the same mechanism.
The cost of this comprehensive enforcement is witness size: Simplicity's trigger and withdraw
transactions are each 298 vB, compared to CAT+CSFS's 221 vB and 210 vB respectively.
Cross-Chain Comparison
Simplicity operates on a fundamentally different chain and trust model. This table shows where the designs align structurally and where they diverge.
|
CTV |
CCV |
OP_VAULT |
CAT+CSFS |
Simplicity Elements |
| Chain |
Bitcoin |
Bitcoin |
Bitcoin |
Bitcoin |
Elements (federated) |
| Trust model |
PoW miners |
PoW miners |
PoW miners |
PoW miners |
Federation (11-of-15) |
| Script model |
P2WSH |
P2TR (Tapscript) |
P2TR (Tapscript) |
P2TR (Tapscript) |
P2TR (Simplicity DAG) |
| Covenant mechanism |
CTV template hash |
CCV opcode |
OP_VAULT opcode |
CSFS + CAT |
jet::outputs_hash() |
| Lifecycle cost |
368 vB |
565 vB |
567 vB |
553 vB |
865 vB |
| Recovery constraint |
Template-locked |
Output-constrained |
Output + key |
None (unconstrained) |
Output-constrained (both paths) |
| Partial withdrawal |
No |
Yes (revault) |
Yes |
No |
No |
| Key model |
2 keys (hot + cold) |
1 key (trigger) |
3 keys (trigger + auth + recovery) |
2 keys (hot + cold) |
2 keys (hot + cold) |
| Fee management |
CPFP anchor |
Integrated |
Fee wallet (2-input) |
Extra input (SIGHASH_SINGLE) |
Explicit fee output |
01
Output-constrained recovery on both paths
Both trigger and recovery paths enforce jet::outputs_hash(). A compromised cold key
can only recover funds to the pre-committed recovery address — it cannot sweep to an arbitrary
destination. This matches CCV and OP_VAULT's recovery security, unlike CAT+CSFS where the cold
key has unconstrained control.
02
Explicit fee outputs eliminate fee pinning
Elements replaces Bitcoin's implicit fee model (input sum minus output sum) with explicit fee
outputs. There are no anchor outputs and no descendant chains to pin. Fee pinning is not just
mitigated by script design — it is architecturally impossible at the protocol level.
03
Typed functional programs with formal verification
Simplicity programs are directed acyclic graphs with denotational semantics and a Coq-verified
type system. Covenant properties can be verified as language-level guarantees, not testing
outcomes. The tradeoff is witness size: the bit-oriented DAG encoding produces 298 vB transactions
where Bitcoin Script equivalents use 94–221 vB.