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 is new in TX_MLSC. It maps each output index to its conditions within the shared condition tree. One conditions_root per transaction defines the entire tree. This replaces the old model where each output carried its own 0xC2 + root.
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, @point) | ADAPTOR_SIG | Signing key + adaptor point. Optional scheme |
| 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–32 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(blocks) | TIMER_CONTINUOUS | Consecutive blocks required |
| timer_off_delay(trigger, hold) | TIMER_OFF_DELAY | Trigger + hold-off blocks |
| latch_set(@pk, state) | LATCH_SET | Key + state value |
| latch_reset(@pk, state) | LATCH_RESET | Key + state value |
| counter_down(@pk, count) | COUNTER_DOWN | Key + counter target |
| counter_preset(@pk, count) | COUNTER_PRESET | Key + preset count |
| counter_up(@pk, count) | COUNTER_UP | Key + counter target |
| compare(op, value_b) | COMPARE | Operator (1=EQ..7=IN_RANGE) + threshold. Optional value_c for IN_RANGE |
| sequencer(steps) | SEQUENCER | Number of sequence steps |
| one_shot(@pk, N) | ONE_SHOT | Key + activation window |
| 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(@sender, @receiver, preimage, csv) | HTLC | Two keys, preimage hex, CSV blocks |
| hash_sig(@pk, preimage) | HASH_SIG | Key + preimage hex |
| ptlc(@pk, @point, csv) | PTLC | Key, adaptor point, CSV blocks |
| cltv_sig(@pk, height) | CLTV_SIG | Signature + absolute timelock |
| timelocked_multisig(M, @k1, ..., csv) | TIMELOCKED_MULTISIG | Threshold, keys, CSV blocks |
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) |
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:
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:
Key aliases are not preserved in the hex (the keys are hashed into the Merkle tree). When formatting back, unknown keys appear as @? or truncated hex.
Three commands to create, sign, and broadcast a Ladder Script transaction on the live signet. This is the wallet integration path.
createtxmlsc builds the transaction, signladder signs it using a descriptor string, sendrawtransaction broadcasts it. The descriptor handles all serialization, Merkle commitment, creation proof, and witness construction internally. No manual field ordering, no JSON block specs. Works for all 61 block types.
If you don't want to run a node, the same flow works via the public proxy API at ladder-script.org: