The bitcoin script

Every on-chain leg is one output: a P2WSH hash time-locked contract with two spends. This page is that script, opcode by opcode, and what each side has to present to use it.

The script

The witness script, before anything is filled in:

OP_IF
    OP_SHA256 <payment_hash> OP_EQUALVERIFY <claimant> OP_CHECKSIG
OP_ELSE
    <refund_locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refunder> OP_CHECKSIG
OP_ENDIF

The two branches are the two ways out. The top one is the claim path - pay whoever proves the preimage. The bottom one is the refund path - give the funder its money back, but not before the locktime. The branch to take is chosen by the last item pushed in the witness.

Built with a real locktime and keys, the same script reads like this in swapx's assembly output:

OP_IF
    OP_SHA256 OP_PUSHBYTES_32 <hash> OP_EQUALVERIFY OP_PUSHBYTES_33 <claimant> OP_CHECKSIG
OP_ELSE
    OP_PUSHBYTES_2 f401 OP_CLTV OP_DROP OP_PUSHBYTES_33 <refunder> OP_CHECKSIG
OP_ENDIF

f401 is the locktime as a little-endian push: two bytes, 0x01fa, which is height 506. OP_CLTV is rust-bitcoin's name for OP_CHECKLOCKTIMEVERIFY.

Wrapped as P2WSH

The script above is not what you send coins to. Its SHA256 hash is, inside a pay-to-witness-script-hash output:

OP_0 <sha256(witness_script)>

The address is bcrt1q… on regtest and bc1q… on mainnet. To spend it, the witness must carry the full script, so the spender reveals the terms it is satisfying rather than hiding them.

The claim path

OP_SHA256 <payment_hash> OP_EQUALVERIFY <claimant> OP_CHECKSIG
  1. OP_SHA256 hashes the preimage that the witness supplies.
  2. OP_EQUALVERIFY checks that hash against the payment hash baked into the script. A wrong preimage fails here, by consensus.
  3. OP_CHECKSIG checks the signature against the claimant's public key. A wrong key fails here.

Both are required. The preimage alone does not spend the output, and neither does the key alone - which is what makes it safe for one side to publish the preimage, as we will see below.

The refund path

<refund_locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refunder> OP_CHECKSIG
  1. OP_CHECKLOCKTIMEVERIFY fails the whole script unless the spending transaction's locktime has reached the height in the script. It does not pop the value; it only inspects it.
  2. OP_DROP discards that height so the stack is clean.
  3. OP_CHECKSIG checks the refunder's signature.

Two things have to line up for the transaction that spends this way: its nLockTime must be at least the script's height, and its input sequence must be below the maximum so the locktime is actually enforced. The refund transaction this project builds uses sequence 0xfffffffe for that reason; the claim uses the maximum, because it has nothing to wait for.

Heights only, never times. The two chains a swap touches would have to agree on a mixed comparison for wall-clock locktimes to be safe, and they do not, so time-based locktimes are rejected when a swap is constructed.

The witnesses

The last item in the witness picks the branch: a 1 takes the OP_IF side, an empty item takes the OP_ELSE side.

SpendWitness
claim <signature> <preimage> <1> <script>
refund <signature> <> <script>

Who holds what

Roles in the script are not parties. claimant means whoever is owed the on-chain leg, and refunder means whoever funded it - and which party that is flips with the direction.

Pay Lightning, receive on chain Pay on chain, receive Lightning
funds the HTLC the maker the taker
claimant the taker's key, made in the page the maker's key, fresh per swap
refunder the maker's key the taker's key
preimage the maker's, revealed by settling the taker's invoice preimage, revealed by paying

The claim path needs two independent things - the preimage and the claimant's signature - and neither side holds both up front. The maker generates the preimage but cannot spend the output with it, because its only exit is the timeout behind its own key.

Why a published preimage is safe

Settling a hold invoice is done by handing over the preimage, and that is exactly what tells the Lightning network the payment is claimable. From that moment the preimage is public, so the maker publishes it on settled swaps rather than making you copy it across by hand.

Publishing it costs nothing, because the preimage on its own does not spend the output: OP_CHECKSIG still asks for the claimant's key, which never leaves the page. The field is absent entirely before the swap settles, so a swap's public view cannot be read as "the secret is out" while it is still in flight.