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
-
OP_SHA256hashes the preimage that the witness supplies. -
OP_EQUALVERIFYchecks that hash against the payment hash baked into the script. A wrong preimage fails here, by consensus. -
OP_CHECKSIGchecks 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
-
OP_CHECKLOCKTIMEVERIFYfails 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. -
OP_DROPdiscards that height so the stack is clean. -
OP_CHECKSIGchecks 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.
| Spend | Witness |
|---|---|
| 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.