Descriptor Notation
A compact human-readable notation for writing Ladder Script spending conditions
Instead of building conditions block by block in JSON, you can write them as a single expression:
This describes a vault: spend with a hot key after 144 blocks, or immediately with any 2 of 3 recovery keys. The notation maps directly to the rung/block structure. or() creates multiple rungs. and() puts multiple blocks in one rung. Each function name is a block type.
output() wrapper maps each output index to its conditions within the shared condition tree. One conditions_root per transaction defines the entire tree (TX_MLSC). The 32-byte root is recovered at spend time from a synthetic UTXO entry — see MLSC Specification.
Every active block type has descriptor notation. Grouped by family:
Signature Family
| Notation | Block type | Arguments |
|---|---|---|
| sig(@alias) | SIG | Key alias. Optional scheme: sig(@alice, falcon512) |
| multisig(M, @k1, @k2, ...) | MULTISIG | Threshold M, then key aliases |
| adaptor_sig(@signer) | ADAPTOR_SIG | Single signing key (v0.7+). Optional scheme: adaptor_sig(@signer, schnorr). Adaptor point T is off-chain only. |
| musig_threshold(M, @k1, @k2, ...) | MUSIG_THRESHOLD | MuSig2/FROST aggregate threshold |
| key_ref_sig(relay, block) | KEY_REF_SIG | Relay index + block index (cross-rung key sharing) |
Timelock Family
| Notation | Block type | Arguments |
|---|---|---|
| csv(N) | CSV | Relative timelock in blocks |
| csv_time(N) | CSV_TIME | Relative timelock in seconds |
| cltv(N) | CLTV | Absolute block height |
| cltv_time(N) | CLTV_TIME | Absolute time (MTP, ≥ 500000000) |
Hash Family
| Notation | Block type | Arguments |
|---|---|---|
| tagged_hash(tag, expected) | TAGGED_HASH | Two 32-byte hashes: BIP-340 tag + expected |
| hash_guarded(hex) | HASH_GUARDED | 32-byte SHA-256 hash commitment |
Covenant Family
| Notation | Block type | Arguments |
|---|---|---|
| ctv(hex) | CTV | 32-byte BIP-119 template hash |
| vault_lock(@recovery, @hot, delay) | VAULT_LOCK | Recovery key, hot key, CSV delay |
| amount_lock(min, max) | AMOUNT_LOCK | Output value range in satoshis |
Recursion Family
| Notation | Block type | Arguments |
|---|---|---|
| recurse_same(depth) | RECURSE_SAME | Max recursion depth |
| recurse_modified(depth, blk, param, delta) | RECURSE_MODIFIED | Depth, block index, param index, mutation delta |
| recurse_until(height) | RECURSE_UNTIL | Terminates at block height |
| recurse_count(count) | RECURSE_COUNT | Countdown; terminates at 0 |
| recurse_split(splits, min_sats) | RECURSE_SPLIT | Max splits, minimum sats per output |
| recurse_decay(depth, blk, param, decay) | RECURSE_DECAY | Depth, block index, param index, decay per step |
Anchor Family
| Notation | Block type | Arguments |
|---|---|---|
| anchor() | ANCHOR | Generic anchor output |
| anchor_channel() | ANCHOR_CHANNEL | Lightning channel anchor |
| anchor_pool() | ANCHOR_POOL | Pool anchor |
| anchor_reserve() | ANCHOR_RESERVE | Reserve anchor (guardian set) |
| anchor_seal() | ANCHOR_SEAL | Seal anchor |
| anchor_oracle() | ANCHOR_ORACLE | Oracle anchor |
| data_return(hex) | DATA_RETURN | 1–40 byte data payload (replaces OP_RETURN) |
PLC Family
| Notation | Block type | Arguments |
|---|---|---|
| hysteresis_fee(low, high) | HYSTERESIS_FEE | Fee band thresholds |
| hysteresis_value(low, high) | HYSTERESIS_VALUE | Value band thresholds |
| timer_continuous(accumulated, target) | TIMER_CONTINUOUS | Accumulated + target blocks (2 numerics) |
| timer_off_delay(remaining) | TIMER_OFF_DELAY | Remaining hold-off blocks (1 numeric) |
| latch_set(@pk, state) | LATCH_SET | Setter key + state value |
| latch_reset(@pk, state, delay) | LATCH_RESET | Resetter key + state value + delay blocks |
| counter_down(@pk, count) | COUNTER_DOWN | Event signer + counter |
| counter_preset(@pk, current, preset) | COUNTER_PRESET | Approval signer + current + preset count |
| counter_up(@pk, current, target) | COUNTER_UP | Event signer + current + target count |
| compare(op, value_b) | COMPARE | Operator (1=EQ..7=IN_RANGE) + threshold. Optional value_c for IN_RANGE |
| sequencer(current_step, total_steps) | SEQUENCER | Current step + total step count (2 numerics) |
| one_shot(@pk, state, commitment_hex) | ONE_SHOT | Key + state value + 32-byte commitment hash |
| rate_limit(max, cap, refill) | RATE_LIMIT | Max per block, accumulation cap, refill blocks |
| cosign(hex) | COSIGN | 32-byte conditions hash of co-input |
Compound Family
| Notation | Block type | Arguments |
|---|---|---|
| timelocked_sig(@pk, csv) | TIMELOCKED_SIG | Signature + relative timelock |
| htlc(@receiver, @sender, preimage | h:HASH256, csv) | HTLC | Receiver key first (path=0 hashlock), sender key second (path=1 refund), preimage hex (auto-SHA256'd) or the h: form supplying the 32-byte hash directly, CSV blocks. The path indicator is added by the witness builder. |
| hash_sig(@pk, preimage | h:HASH256) | HASH_SIG | Key + preimage hex (auto-SHA256'd) or the h: form supplying the 32-byte hash directly. |
| ptlc(@pk, csv) | PTLC | Single signing key (v0.7+) + CSV blocks. Adaptor point T is off-chain only. |
| cltv_sig(@pk, height) | CLTV_SIG | Signature + absolute timelock |
| timelocked_multisig(M, @k1, ..., csv) | TIMELOCKED_MULTISIG | Threshold, keys, CSV blocks |
| anchor_fee(@local, @remote, min_fee, max_fee, max_weight, commitment) | ANCHOR_FEE | 2-of-2 sigs + fee rate band + weight limit + spender commitment numeric (anti-pinning L2 anchor) |
Governance Family
| Notation | Block type | Arguments |
|---|---|---|
| epoch_gate(epoch_size, window) | EPOCH_GATE | Blocks per epoch, window size |
| weight_limit(max) | WEIGHT_LIMIT | Maximum transaction weight |
| input_count(min, max) | INPUT_COUNT | Input count bounds |
| output_count(min, max) | OUTPUT_COUNT | Output count bounds |
| relative_value(num, den) | RELATIVE_VALUE | Numerator/denominator ratio |
| accumulator(root) | ACCUMULATOR | 32-byte Merkle root |
| output_check(idx, min, max, hex) | OUTPUT_CHECK | Output index, value range, script hash |
Legacy Family
| Notation | Block type | Arguments |
|---|---|---|
| p2pk(@pk) | P2PK_LEGACY | Public key |
| p2pkh(@pk) | P2PKH_LEGACY | Public key (hashed to HASH160) |
| p2sh(hex) | P2SH_LEGACY | Inner conditions hex |
| p2wpkh(@pk) | P2WPKH_LEGACY | Public key (SegWit) |
| p2wsh(hex) | P2WSH_LEGACY | Inner conditions hex |
| p2tr(@pk) | P2TR_LEGACY | Internal key (Taproot key-path) |
| p2tr_script(hex) | P2TR_SCRIPT_LEGACY | Inner script hex (Taproot script-path) |
QABI / PQ Family
| Notation | Block type | Arguments |
|---|---|---|
| qabi_prime() | QABI_PRIME | Priming marker — no committed fields |
| qabi_spend(auth_tip, root, depth, expiry, owner_id) | QABI_SPEND | Batch-spend gate: auth chain tip, committed root, depth, expiry, owner id (all hex) |
| pq_batch(pubkey_hash) | PQ_BATCH | 32-byte SHA256(falcon_pubkey) — anchor input reveals pubkey + sig once per tx |
The optional second argument to sig() selects the signature scheme:
| Name | Scheme | Example |
|---|---|---|
| schnorr | BIP-340 Schnorr (default) | sig(@alice) |
| ecdsa | ECDSA | sig(@alice, ecdsa) |
| falcon512 | FALCON-512 (PQ) | sig(@alice, falcon512) |
| falcon1024 | FALCON-1024 (PQ) | sig(@alice, falcon1024) |
| dilithium3 | Dilithium3 (PQ) | sig(@alice, dilithium3) |
| sphincs_sha | SPHINCS+-SHA2 (PQ) | sig(@alice, sphincs_sha) |
Two RPC commands work with descriptor notation:
pubkeys_hex + key map + merkle_pubkeys_hex from parseladder to render @aliases faithfully (see the Round-trip section below).Aliases like @alice are resolved from a JSON key map passed as the second argument to parseladder. The map contains alias names and their compressed public keys:
Different aliases with different keys produce different MLSC roots. The keys are folded into the Merkle leaf hash via merkle_pub_key, so they never appear in the on-chain output.
Descriptors round-trip through parseladder and formatladder:
Public keys are folded into the Merkle leaf hash and not present in the conditions blob. To round-trip a descriptor that names @aliases, pass parseladder's pubkeys_hex and merkle_pubkeys_hex arrays plus the original key map back to formatladder:
If you call formatladder with only the conditions hex (no pubkey side-data), pubkey-bearing blocks render with truncated-hex placeholders that won't reparse. The pubkeys_hex + merkle_pubkeys_hex + key-map combination is required for descriptors that name keys (sig, multisig, htlc, vault_lock, anchor_fee, timelocked_sig, ptlc, etc.).
Three commands to create, sign, and broadcast a Ladder Script transaction on the live signet. This is the wallet integration path.
createrungtx builds the transaction, signladder signs it using a descriptor string, sendrawtransaction broadcasts it. The descriptor handles all serialisation, Merkle commitment, and witness construction internally. No manual field ordering, no JSON block specs. Works for all 65 block types.
If you don't want to run a node, the same flow works via the public proxy API at ladder-script.org: